From c8ddb7a47cf1a067147cd3dd1dc4b523f60a4f6f Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Tue, 19 Sep 2023 17:11:58 +0200 Subject: [PATCH 1/2] Make the Voucher view more comforming to designs --- ios/MullvadVPN.xcodeproj/project.pbxproj | 6 +- .../CreateAccountVoucherCoordinator.swift | 10 +++- .../ProfileVoucherCoordinator.swift | 6 +- .../UIEdgeInsets+Extensions.swift | 21 +++++++ ios/MullvadVPN/UI appearance/UIMetrics.swift | 3 +- .../RedeemVoucher/LogoutDialogueView.swift | 2 +- .../RedeemVoucherContentView.swift | 57 ++++++++++++------- .../RedeemVoucherViewConfiguration.swift | 6 ++ 8 files changed, 86 insertions(+), 25 deletions(-) create mode 100644 ios/MullvadVPN/UI appearance/UIEdgeInsets+Extensions.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 77995bebf67e..07fa564ba77b 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -472,6 +472,7 @@ A9D99BA52A1F808900DE27D3 /* RelayCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 063F02732902B63F001FA09F /* RelayCache.framework */; }; A9D99BA62A1F809C00DE27D3 /* libRelaySelector.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5898D29829017DAC00EB5EBA /* libRelaySelector.a */; }; A9D99BA92A1F81B700DE27D3 /* MullvadTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */; }; + A9E034642ABB302000E59A5A /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */; }; A9EC20E62A5C488D0040D56E /* Haversine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EC20E52A5C488D0040D56E /* Haversine.swift */; }; A9EC20E82A5D3A8C0040D56E /* CoordinatesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */; }; A9EC20F02A5D79ED0040D56E /* TunnelObfuscation.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5840231F2A406BF5007B27AC /* TunnelObfuscation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -1438,6 +1439,7 @@ A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCacheTests.swift; sourceTree = ""; }; A9D96B192A8247C100A5C673 /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = ""; }; A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportProvider.swift; sourceTree = ""; }; + A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Extensions.swift"; sourceTree = ""; }; A9EC20E52A5C488D0040D56E /* Haversine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Haversine.swift; sourceTree = ""; }; A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesTests.swift; sourceTree = ""; }; A9EC20F32A5D96030040D56E /* Midpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Midpoint.swift; sourceTree = ""; }; @@ -2073,8 +2075,9 @@ 583FE02729C1ADF7006E85F9 /* UI appearance */ = { isa = PBXGroup; children = ( - 585CA70E25F8C44600B47C62 /* UIMetrics.swift */, 58CCA0152242560B004F3011 /* UIColor+Palette.swift */, + A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */, + 585CA70E25F8C44600B47C62 /* UIMetrics.swift */, ); path = "UI appearance"; sourceTree = ""; @@ -4045,6 +4048,7 @@ 58CE5E64224146200008646E /* AppDelegate.swift in Sources */, F0DA87492A9CBA9F006044F1 /* AccountDeviceRow.swift in Sources */, 5878A27329091D6D0096FC88 /* TunnelBlockObserver.swift in Sources */, + A9E034642ABB302000E59A5A /* UIEdgeInsets+Extensions.swift in Sources */, 58E0A98827C8F46300FE6BDD /* Tunnel.swift in Sources */, 58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */, 58EE2E3A272FF814003BFF93 /* SettingsDataSource.swift in Sources */, diff --git a/ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift b/ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift index 1407c61808e3..50beb1ac5592 100644 --- a/ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/CreateAccountVoucherCoordinator.swift @@ -26,8 +26,16 @@ public class CreateAccountVoucherCoordinator: Coordinator { self.navigationController = navigationController self.interactor = interactor + var layoutMargins = navigationController.view.layoutMargins.toDirectionalInsets + layoutMargins.top += UIMetrics.contentLayoutMargins.top + layoutMargins.bottom += UIMetrics.contentLayoutMargins.bottom + viewController = RedeemVoucherViewController( - configuration: RedeemVoucherViewConfiguration(adjustViewWhenKeyboardAppears: true), + configuration: RedeemVoucherViewConfiguration( + adjustViewWhenKeyboardAppears: true, + shouldUseCompactStyle: false, + layoutMargins: layoutMargins + ), interactor: interactor ) } diff --git a/ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift b/ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift index 8e166985b900..61fbcd7e9455 100644 --- a/ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/ProfileVoucherCoordinator.swift @@ -24,7 +24,11 @@ final class ProfileVoucherCoordinator: Coordinator, Presentable { ) { self.navigationController = navigationController viewController = RedeemVoucherViewController( - configuration: RedeemVoucherViewConfiguration(adjustViewWhenKeyboardAppears: false), + configuration: RedeemVoucherViewConfiguration( + adjustViewWhenKeyboardAppears: false, + shouldUseCompactStyle: true, + layoutMargins: UIMetrics.SettingsRedeemVoucher.contentLayoutMargins + ), interactor: interactor ) } diff --git a/ios/MullvadVPN/UI appearance/UIEdgeInsets+Extensions.swift b/ios/MullvadVPN/UI appearance/UIEdgeInsets+Extensions.swift new file mode 100644 index 000000000000..8a89bb99a946 --- /dev/null +++ b/ios/MullvadVPN/UI appearance/UIEdgeInsets+Extensions.swift @@ -0,0 +1,21 @@ +// +// UIEdgeInsets+Extensions.swift +// MullvadVPN +// +// Created by Marco Nikic on 2023-09-20. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import UIKit + +extension UIEdgeInsets { + var toDirectionalInsets: NSDirectionalEdgeInsets { + NSDirectionalEdgeInsets( + top: top, + leading: left, + bottom: bottom, + trailing: right + ) + } +} diff --git a/ios/MullvadVPN/UI appearance/UIMetrics.swift b/ios/MullvadVPN/UI appearance/UIMetrics.swift index b257d5d46a8d..68b071959a73 100644 --- a/ios/MullvadVPN/UI appearance/UIMetrics.swift +++ b/ios/MullvadVPN/UI appearance/UIMetrics.swift @@ -38,7 +38,7 @@ enum UIMetrics { enum SettingsRedeemVoucher { static let cornerRadius = 8.0 static let preferredContentSize = CGSize(width: 280, height: 260) - static let contentLayoutMargins = NSDirectionalEdgeInsets(top: 16, leading: 0, bottom: 16, trailing: 0) + static let contentLayoutMargins = NSDirectionalEdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16) } enum AccountDeletion { @@ -130,6 +130,7 @@ extension UIMetrics { /// Various paddings used throughout the app to visually separate elements in StackViews static let padding4: CGFloat = 4 static let padding8: CGFloat = 8 + static let padding10: CGFloat = 10 static let padding16: CGFloat = 16 static let padding24: CGFloat = 24 static let padding32: CGFloat = 32 diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift index a6aee49cb53b..c1c192ad667b 100644 --- a/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift +++ b/ios/MullvadVPN/View controllers/RedeemVoucher/LogoutDialogueView.swift @@ -20,7 +20,7 @@ class LogoutDialogueView: UIView { private let messageLabel: UILabel = { let label = UILabel() - label.font = .preferredFont(forTextStyle: .callout, weight: .light) + label.font = .preferredFont(forTextStyle: .callout, weight: .semibold) label.numberOfLines = .zero label.lineBreakMode = .byWordWrapping label.textColor = .white diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherContentView.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherContentView.swift index f5061d570d26..7075dd7c4d90 100644 --- a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherContentView.swift +++ b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherContentView.swift @@ -30,9 +30,26 @@ final class RedeemVoucherContentView: UIView { return contentHolderView }() - private let titleLabel: UILabel = { + private let voucherTextFieldHeight: CGFloat = 54 + + private let title: UILabel = { let label = UILabel() - label.font = .preferredFont(forTextStyle: .body) + label.font = .preferredFont(forTextStyle: .title1, weight: .bold).withSize(32) + label.text = NSLocalizedString( + "REDEEM_VOUCHER_TITLE", + tableName: "RedeemVoucher", + value: "Redeem voucher", + comment: "" + ) + label.textColor = .white + label.numberOfLines = 0 + return label + }() + + private let enterVoucherLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .body, weight: .semibold).withSize(15) + label.text = NSLocalizedString( "REDEEM_VOUCHER_INSTRUCTION", tableName: "RedeemVoucher", @@ -40,7 +57,6 @@ final class RedeemVoucherContentView: UIView { comment: "" ) label.textColor = .white - label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 0 return label }() @@ -61,7 +77,6 @@ final class RedeemVoucherContentView: UIView { private let activityIndicator: SpinnerActivityIndicatorView = { let activityIndicator = SpinnerActivityIndicatorView(style: .medium) - activityIndicator.translatesAutoresizingMaskIntoConstraints = false activityIndicator.tintColor = .white activityIndicator.setContentHuggingPriority(.defaultHigh, for: .horizontal) activityIndicator.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) @@ -70,7 +85,7 @@ final class RedeemVoucherContentView: UIView { private let statusLabel: UILabel = { let label = UILabel() - label.font = .preferredFont(forTextStyle: .body) + label.font = .systemFont(ofSize: 13, weight: .semibold) label.numberOfLines = 2 label.lineBreakMode = .byWordWrapping label.textColor = .red @@ -111,32 +126,36 @@ final class RedeemVoucherContentView: UIView { private lazy var statusStack: UIStackView = { let stackView = UIStackView(arrangedSubviews: [activityIndicator, statusLabel]) - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.axis = .horizontal stackView.spacing = UIMetrics.padding8 return stackView }() private lazy var voucherCodeStackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [ - titleLabel, + var arrangedSubviews = [ + enterVoucherLabel, textField, statusStack, logoutViewForAccountNumberIsEntered, - ]) - stackView.translatesAutoresizingMaskIntoConstraints = false + ] + + if configuration.shouldUseCompactStyle == false { + arrangedSubviews.insert(title, at: 0) + } + + let stackView = UIStackView(arrangedSubviews: arrangedSubviews) stackView.axis = .vertical - stackView.setCustomSpacing(UIMetrics.padding16, after: titleLabel) + stackView.setCustomSpacing(UIMetrics.padding8, after: title) + stackView.setCustomSpacing(UIMetrics.padding16, after: enterVoucherLabel) stackView.setCustomSpacing(UIMetrics.padding8, after: textField) stackView.setCustomSpacing(UIMetrics.padding16, after: statusLabel) - stackView.setCustomSpacing(UIMetrics.padding24, after: statusStack) + stackView.setCustomSpacing(UIMetrics.padding10, after: statusStack) stackView.setContentHuggingPriority(.defaultLow, for: .vertical) + return stackView }() private lazy var buttonsStackView: UIStackView = { let stackView = UIStackView(arrangedSubviews: [redeemButton, cancelButton]) - stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .vertical stackView.spacing = UIMetrics.padding16 stackView.setContentCompressionResistancePriority(.required, for: .vertical) @@ -238,9 +257,7 @@ final class RedeemVoucherContentView: UIView { } required init?(coder: NSCoder) { - self.configuration = RedeemVoucherViewConfiguration(adjustViewWhenKeyboardAppears: true) - super.init(coder: coder) - commonInit() + fatalError("init(coder:) has not been implemented") } private func commonInit() { @@ -260,13 +277,13 @@ final class RedeemVoucherContentView: UIView { private func configureUI() { addConstrainedSubviews([scrollView]) { - scrollView.pinEdgesToSuperviewMargins() + scrollView.pinEdgesToSuperview(.all(configuration.layoutMargins)) } scrollView.addConstrainedSubviews([contentHolderView]) { contentHolderView.pinEdgesToSuperview() - contentHolderView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0) - contentHolderView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor, multiplier: 1.0) + contentHolderView.widthAnchor.constraint(equalTo: scrollView.widthAnchor) + contentHolderView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor) } contentHolderView.addConstrainedSubviews([voucherCodeStackView, buttonsStackView]) { voucherCodeStackView.pinEdgesToSuperview(.all().excluding(.bottom)) diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewConfiguration.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewConfiguration.swift index 32052230da23..136c7a6f2876 100644 --- a/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewConfiguration.swift +++ b/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherViewConfiguration.swift @@ -7,6 +7,12 @@ // import Foundation +import UIKit + struct RedeemVoucherViewConfiguration { let adjustViewWhenKeyboardAppears: Bool + /// Hides the title when set to `true`. + let shouldUseCompactStyle: Bool + /// Custom margins to use for the compact style. + let layoutMargins: NSDirectionalEdgeInsets } From 55263e1e630ba46555576be3b895df2fb7de91ae Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Thu, 21 Sep 2023 11:17:04 +0200 Subject: [PATCH 2/2] Fix the successful redeem as well --- ios/MullvadVPN/UI appearance/UIMetrics.swift | 1 + .../AddCreditSucceededViewController.swift | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ios/MullvadVPN/UI appearance/UIMetrics.swift b/ios/MullvadVPN/UI appearance/UIMetrics.swift index 68b071959a73..9538f14a8669 100644 --- a/ios/MullvadVPN/UI appearance/UIMetrics.swift +++ b/ios/MullvadVPN/UI appearance/UIMetrics.swift @@ -39,6 +39,7 @@ enum UIMetrics { static let cornerRadius = 8.0 static let preferredContentSize = CGSize(width: 280, height: 260) static let contentLayoutMargins = NSDirectionalEdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16) + static let successfulRedeemMargins = NSDirectionalEdgeInsets(top: 16, leading: 8, bottom: 16, trailing: 8) } enum AccountDeletion { diff --git a/ios/MullvadVPN/View controllers/RedeemVoucher/AddCreditSucceededViewController.swift b/ios/MullvadVPN/View controllers/RedeemVoucher/AddCreditSucceededViewController.swift index 8ddf300f3eef..1176075b93b4 100644 --- a/ios/MullvadVPN/View controllers/RedeemVoucher/AddCreditSucceededViewController.swift +++ b/ios/MullvadVPN/View controllers/RedeemVoucher/AddCreditSucceededViewController.swift @@ -97,19 +97,18 @@ class AddCreditSucceededViewController: UIViewController, RootContainment { override func viewDidLoad() { super.viewDidLoad() - addSubviews() - addConstraints() + configureUI() addDismissButtonHandler() } - private func addSubviews() { - for subview in [statusImageView, titleLabel, messageLabel, dismissButton] { - view.addSubview(subview) + private func configureUI() { + let contentHolderView = UIView(frame: .zero) + + view.addConstrainedSubviews([contentHolderView]) { + contentHolderView.pinEdgesToSuperview(.all(UIMetrics.SettingsRedeemVoucher.successfulRedeemMargins)) } - } - private func addConstraints() { - view.addConstrainedSubviews([statusImageView, titleLabel, messageLabel, dismissButton]) { + contentHolderView.addConstrainedSubviews([statusImageView, titleLabel, messageLabel, dismissButton]) { statusImageView.pinEdgesToSuperviewMargins(PinnableEdges([.top(0)])) statusImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor)