diff options
| author | Andreas Lif <andreas.lif@shortcut.io> | 2022-06-10 14:39:58 +0200 |
|---|---|---|
| committer | Andreas Lif <andreas.lif@shortcut.io> | 2022-06-15 15:17:29 +0200 |
| commit | e4d79bd24eeb388520604364321cd9efff26c10c (patch) | |
| tree | a0a8ec3d4d8b2f99a2d2a97198a222eeb50a4adb | |
| parent | b159c406634f6adbc309e00a57fa09299f94c037 (diff) | |
| download | mullvadvpn-e4d79bd24eeb388520604364321cd9efff26c10c.tar.xz mullvadvpn-e4d79bd24eeb388520604364321cd9efff26c10c.zip | |
Add last used account to login view
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 8 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountInputGroupView.swift | 191 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountViewController.swift | 8 | ||||
| -rw-r--r-- | ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/Contents.json | 15 | ||||
| -rw-r--r-- | ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/IconCloseSml.pdf | bin | 0 -> 1137 bytes | |||
| -rw-r--r-- | ios/MullvadVPN/LoginViewController.swift | 67 | ||||
| -rw-r--r-- | ios/MullvadVPN/StringFormatter.swift | 15 | ||||
| -rw-r--r-- | ios/MullvadVPN/UIColor+Palette.swift | 6 | ||||
| -rwxr-xr-x | ios/convert-assets.rb | 3 |
9 files changed, 263 insertions, 50 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index b1535c0fa4..d41d678a23 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -258,7 +258,6 @@ 58D0C7A223F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D0C7A023F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift */; }; 58D67A0A26D7AE3300557C3C /* OSLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5823FA4F26CA690600283BF8 /* OSLogHandler.swift */; }; 58DF28A52417CB4B00E836B0 /* AppStorePaymentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF28A42417CB4B00E836B0 /* AppStorePaymentManager.swift */; }; - 58DF5B7F2852778600E92647 /* OperationSmokeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF5B7E2852778600E92647 /* OperationSmokeTests.swift */; }; 58DF5B742851FF3F00E92647 /* InputOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF5B732851FF3F00E92647 /* InputOperation.swift */; }; 58DF5B762852108E00E92647 /* InputInjectionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF5B752852108E00E92647 /* InputInjectionBuilder.swift */; }; 58DF5B782852178600E92647 /* OperationInputInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF5B772852178600E92647 /* OperationInputInjectionTests.swift */; }; @@ -267,6 +266,7 @@ 58DF5B7B285217FE00E92647 /* InputOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF5B732851FF3F00E92647 /* InputOperation.swift */; }; 58DF5B7C28521A9F00E92647 /* ResultOperation+Output.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58059DDF2846823E002B1049 /* ResultOperation+Output.swift */; }; 58DF5B7D28521AAC00E92647 /* OutputOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58059DDD28468158002B1049 /* OutputOperation.swift */; }; + 58DF5B7F2852778600E92647 /* OperationSmokeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF5B7E2852778600E92647 /* OperationSmokeTests.swift */; }; 58E0A98827C8F46300FE6BDD /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E0A98727C8F46300FE6BDD /* Tunnel.swift */; }; 58E20771274672CA00DE5D77 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E20770274672CA00DE5D77 /* LaunchViewController.swift */; }; 58E6771F24ADFE7800AA26E7 /* SettingsNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E6771E24ADFE7800AA26E7 /* SettingsNavigationController.swift */; }; @@ -305,6 +305,7 @@ 58FEEB46260A028D00A621A8 /* GeoJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEEB45260A028D00A621A8 /* GeoJSON.swift */; }; 58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */; }; 58FF2C03281BDE02009EF542 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF2C02281BDE02009EF542 /* SettingsManager.swift */; }; + E158B360285381C60002F069 /* StringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* StringFormatter.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -544,10 +545,10 @@ 58D0C79F23F1CECF00FE9BA7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 58D0C7A023F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MullvadVPNScreenshots.swift; sourceTree = "<group>"; }; 58DF28A42417CB4B00E836B0 /* AppStorePaymentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorePaymentManager.swift; sourceTree = "<group>"; }; - 58DF5B7E2852778600E92647 /* OperationSmokeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationSmokeTests.swift; sourceTree = "<group>"; }; 58DF5B732851FF3F00E92647 /* InputOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputOperation.swift; sourceTree = "<group>"; }; 58DF5B752852108E00E92647 /* InputInjectionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputInjectionBuilder.swift; sourceTree = "<group>"; }; 58DF5B772852178600E92647 /* OperationInputInjectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationInputInjectionTests.swift; sourceTree = "<group>"; }; + 58DF5B7E2852778600E92647 /* OperationSmokeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationSmokeTests.swift; sourceTree = "<group>"; }; 58E0A98727C8F46300FE6BDD /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = "<group>"; }; 58E20770274672CA00DE5D77 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = "<group>"; }; 58E6771E24ADFE7800AA26E7 /* SettingsNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsNavigationController.swift; sourceTree = "<group>"; }; @@ -584,6 +585,7 @@ 58FEEB45260A028D00A621A8 /* GeoJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoJSON.swift; sourceTree = "<group>"; }; 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomaticKeyboardResponder.swift; sourceTree = "<group>"; }; 58FF2C02281BDE02009EF542 /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = "<group>"; }; + E158B35F285381C60002F069 /* StringFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringFormatter.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -961,6 +963,7 @@ 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */, 58EF581025D69DB400AEBA94 /* StatusImageView.swift */, 5807E2BF2432038B00F5FF30 /* String+Split.swift */, + E158B35F285381C60002F069 /* StringFormatter.swift */, 5871FB8225498CA20051A0A4 /* Swizzle.swift */, 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */, 58E0A98727C8F46300FE6BDD /* Tunnel.swift */, @@ -1332,6 +1335,7 @@ 588BCF282816D664009ADCEC /* RESTResponseHandler.swift in Sources */, 58554F77280AFD5C00013055 /* RESTTaskIdentifier.swift in Sources */, 58BFA5C622A7C97F00A6173D /* RelayCacheTracker.swift in Sources */, + E158B360285381C60002F069 /* StringFormatter.swift in Sources */, 582BB1B1229569620055B6EF /* CustomNavigationBar.swift in Sources */, 58B3F30F2742708B00A2DD38 /* HeaderBarButton.swift in Sources */, 584789E026529D72000E45FB /* SSLPinningURLSessionDelegate.swift in Sources */, diff --git a/ios/MullvadVPN/AccountInputGroupView.swift b/ios/MullvadVPN/AccountInputGroupView.swift index c55b374190..e587679f7a 100644 --- a/ios/MullvadVPN/AccountInputGroupView.swift +++ b/ios/MullvadVPN/AccountInputGroupView.swift @@ -7,6 +7,12 @@ // import UIKit +import Logging + +protocol AccountInputGroupViewDelegate: AnyObject { + func accountInputGroupViewShouldRemoveLastUsedAccount(_ view: AccountInputGroupView) -> Bool + func accountInputGroupViewShouldAttemptLogin(_ view: AccountInputGroupView) +} class AccountInputGroupView: UIView { @@ -14,7 +20,7 @@ class AccountInputGroupView: UIView { case normal, error, authenticating } - var onSendButton: ((AccountInputGroupView) -> Void)? + weak var delegate: AccountInputGroupViewDelegate? let sendButton: UIButton = { let button = UIButton(type: .custom) @@ -45,7 +51,7 @@ class AccountInputGroupView: UIView { private let privateTextField: AccountTextField = { let textField = AccountTextField() - textField.font = UIFont.systemFont(ofSize: 20) + textField.font = accountNumberFont() textField.translatesAutoresizingMaskIntoConstraints = false textField.placeholder = "0000 0000 0000 0000" textField.placeholderTextColor = .lightGray @@ -64,9 +70,55 @@ class AccountInputGroupView: UIView { return textField }() + private let separator: UIView = { + let separator = UIView() + separator.translatesAutoresizingMaskIntoConstraints = false + separator.backgroundColor = UIColor.AccountTextField.NormalState.borderColor + return separator + }() + + private let topRowView: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + view.backgroundColor = .white + + return view + }() + + private let bottomRowView: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + view.backgroundColor = .white.withAlphaComponent(0.8) + + return view + }() + + private let lastUsedAccountButton: UIButton = { + let button = UIButton(type: .system) + button.translatesAutoresizingMaskIntoConstraints = false + button.titleLabel?.font = accountNumberFont() + button.setTitle("", for: .normal) + button.contentHorizontalAlignment = .leading + button.titleEdgeInsets = UIEdgeInsets(top: 0, left: UIMetrics.textFieldMargins.left, bottom: 0, right: 0) + button.setTitleColor(UIColor.AccountTextField.NormalState.textColor, for: .normal) + + return button + }() + + private let removeLastUsedAccountButton: UIButton = { + let button = UIButton(type: .custom) + button.translatesAutoresizingMaskIntoConstraints = false + button.setImage(UIImage(named: "IconCloseSml"), for: .normal) + button.imageView?.tintColor = .primaryColor.withAlphaComponent(0.4) + + return button + }() + private let contentView: UIView = { let view = UIView() + view.backgroundColor = .clear view.translatesAutoresizingMaskIntoConstraints = false + return view }() @@ -75,12 +127,14 @@ class AccountInputGroupView: UIView { private let borderRadius = CGFloat(8) private let borderWidth = CGFloat(2) + private var lastUsedAccount: String = "" + private var borderColor: UIColor { switch loginState { case .default: return privateTextField.isEditing - ? UIColor.AccountTextField.NormalState.borderColor - : UIColor.clear + ? UIColor.AccountTextField.NormalState.borderColor + : UIColor.clear case .failure: return UIColor.AccountTextField.ErrorState.borderColor @@ -119,33 +173,77 @@ class AccountInputGroupView: UIView { private let borderLayer = CAShapeLayer() private let contentLayerMask = CALayer() + var lastUsedAccountViewHeightConstraint: NSLayoutConstraint! + var lastUsedAccountHeightConstraint: NSLayoutConstraint! + var separatorHeightConstraint: NSLayoutConstraint! + // MARK: - View lifecycle override init(frame: CGRect) { super.init(frame: frame) addSubview(contentView) - contentView.addSubview(privateTextField) - contentView.addSubview(sendButton) + contentView.addSubview(topRowView) + contentView.addSubview(bottomRowView) + topRowView.addSubview(privateTextField) + topRowView.addSubview(sendButton) + bottomRowView.addSubview(separator) + bottomRowView.addSubview(lastUsedAccountButton) + bottomRowView.addSubview(removeLastUsedAccountButton) privateTextField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) sendButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + separatorHeightConstraint = separator.heightAnchor.constraint(equalToConstant: 0) + lastUsedAccountHeightConstraint = lastUsedAccountButton.heightAnchor.constraint(equalToConstant: 0) + + lastUsedAccountButton.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + removeLastUsedAccountButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + NSLayoutConstraint.activate([ contentView.topAnchor.constraint(equalTo: topAnchor), - contentView.bottomAnchor.constraint(equalTo: bottomAnchor), contentView.leadingAnchor.constraint(equalTo: leadingAnchor), contentView.trailingAnchor.constraint(equalTo: trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: bottomAnchor), - sendButton.topAnchor.constraint(equalTo: contentView.topAnchor), - sendButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - sendButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - sendButton.widthAnchor.constraint(equalTo: sendButton.heightAnchor), + topRowView.topAnchor.constraint(equalTo: contentView.topAnchor), + topRowView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + topRowView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + topRowView.bottomAnchor.constraint(equalTo: bottomRowView.topAnchor), - privateTextField.topAnchor.constraint(equalTo: contentView.topAnchor), - privateTextField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + bottomRowView.topAnchor.constraint(equalTo: topRowView.bottomAnchor), + bottomRowView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + bottomRowView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + bottomRowView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + + privateTextField.topAnchor.constraint(equalTo: topRowView.topAnchor), + privateTextField.leadingAnchor.constraint(equalTo: topRowView.leadingAnchor), privateTextField.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor), - privateTextField.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + privateTextField.bottomAnchor.constraint(equalTo: topRowView.bottomAnchor), + + sendButton.topAnchor.constraint(equalTo: topRowView.topAnchor), + sendButton.trailingAnchor.constraint(equalTo: topRowView.trailingAnchor), + sendButton.bottomAnchor.constraint(equalTo: topRowView.bottomAnchor), + sendButton.widthAnchor.constraint(equalTo: sendButton.heightAnchor), + + separator.topAnchor.constraint(equalTo: bottomRowView.topAnchor), + separator.bottomAnchor.constraint(equalTo: lastUsedAccountButton.topAnchor), + separator.leadingAnchor.constraint(equalTo: bottomRowView.leadingAnchor), + separator.trailingAnchor.constraint(equalTo: bottomRowView.trailingAnchor), + separatorHeightConstraint, + + lastUsedAccountButton.topAnchor.constraint(equalTo: separator.bottomAnchor), + lastUsedAccountButton.bottomAnchor.constraint(equalTo: bottomRowView.bottomAnchor), + lastUsedAccountButton.leadingAnchor.constraint(equalTo: bottomRowView.leadingAnchor), + lastUsedAccountButton.trailingAnchor.constraint(equalTo: removeLastUsedAccountButton.leadingAnchor), + lastUsedAccountButton.heightAnchor.constraint(lessThanOrEqualTo: privateTextField.heightAnchor), + lastUsedAccountHeightConstraint, + + removeLastUsedAccountButton.topAnchor.constraint(equalTo: separator.bottomAnchor), + removeLastUsedAccountButton.leadingAnchor.constraint(equalTo: lastUsedAccountButton.trailingAnchor), + removeLastUsedAccountButton.trailingAnchor.constraint(equalTo: bottomRowView.trailingAnchor), + removeLastUsedAccountButton.bottomAnchor.constraint(equalTo: bottomRowView.bottomAnchor), + removeLastUsedAccountButton.widthAnchor.constraint(equalTo: removeLastUsedAccountButton.heightAnchor), ]) backgroundColor = UIColor.clear @@ -160,6 +258,10 @@ class AccountInputGroupView: UIView { updateSendButtonAppearance(animated: false) updateKeyboardReturnKeyEnabled() + lastUsedAccountButton.addTarget(self, action: #selector(didTapLastUsedAccount), for: .touchUpInside) + + removeLastUsedAccountButton.addTarget(self, action: #selector(didTapRemoveLastUsedAccount), for: .touchUpInside) + addTextFieldNotificationObservers() addAccessibilityNotificationObservers() sendButton.addTarget(self, action: #selector(handleSendButton(_:)), for: .touchUpInside) @@ -175,6 +277,7 @@ class AccountInputGroupView: UIView { updateAppearance() updateTextFieldEnabled() updateSendButtonAppearance(animated: animated) + updateLastUsedAccount() } func setOnReturnKey(_ onReturnKey: ((AccountInputGroupView) -> Bool)?) { @@ -189,16 +292,22 @@ class AccountInputGroupView: UIView { } } - func setToken(_ token: String) { - privateTextField.autoformattingText = token + func setAccount(_ account: String) { + privateTextField.autoformattingText = account updateSendButtonAppearance(animated: false) } - func clearToken() { + func clearAccount() { privateTextField.autoformattingText = "" updateSendButtonAppearance(animated: false) } + func setLastUsedAccount(_ accountNumber: String) { + lastUsedAccount = accountNumber + lastUsedAccountButton.setTitle(accountNumber, for: .normal) + setLastUsedAccount(isExpanded: true) + } + // MARK: - CALayerDelegate override func layoutSublayers(of layer: CALayer) { @@ -236,11 +345,33 @@ class AccountInputGroupView: UIView { } @objc private func handleSendButton(_ sender: Any) { - onSendButton?(self) + self.delegate?.accountInputGroupViewShouldAttemptLogin(self) + } + + @objc private func didTapLastUsedAccount() { + setAccount(lastUsedAccount) + privateTextField.resignFirstResponder() + setLastUsedAccount(isExpanded: false) + self.delegate?.accountInputGroupViewShouldAttemptLogin(self) + } + + @objc private func didTapRemoveLastUsedAccount() { + if self.delegate?.accountInputGroupViewShouldRemoveLastUsedAccount(self) ?? false { + clearAccount() + setLastUsedAccount(isExpanded: false) + } } // MARK: - Private + private static func accountNumberFont() -> UIFont { + if #available(iOS 13, *) { + return UIFont.monospacedSystemFont(ofSize: 20, weight: .regular) + } else { + return UIFont.systemFont(ofSize: 20) + } + } + private func addTextFieldNotificationObservers() { let notificationCenter = NotificationCenter.default @@ -260,7 +391,7 @@ class AccountInputGroupView: UIView { private func updateAppearance() { borderLayer.strokeColor = borderColor.cgColor - contentView.backgroundColor = backgroundLayerColor + topRowView.backgroundColor = backgroundLayerColor privateTextField.textColor = textColor } @@ -274,6 +405,28 @@ class AccountInputGroupView: UIView { } } + private func updateLastUsedAccount() { + guard !lastUsedAccount.isEmpty else { + setLastUsedAccount(isExpanded: false) + return + } + switch self.loginState { + case .authenticating, .success: + setLastUsedAccount(isExpanded: false) + default: + setLastUsedAccount(isExpanded: true) + } + } + + private func setLastUsedAccount(isExpanded: Bool) { + lastUsedAccountHeightConstraint.constant = isExpanded ? 50 : 0 + lastUsedAccountButton.alpha = isExpanded ? 1 : 0 + lastUsedAccountButton.isUserInteractionEnabled = isExpanded ? true : false + + separatorHeightConstraint.constant = isExpanded ? 2 : 0 + separator.alpha = isExpanded ? 1 : 0 + } + private func updateSendButtonAppearance(animated: Bool) { let actions = { switch self.loginState { diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift index c0fe6a33a8..4a4dd542ed 100644 --- a/ios/MullvadVPN/AccountViewController.swift +++ b/ios/MullvadVPN/AccountViewController.swift @@ -85,7 +85,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb ) contentView.accountTokenRowView.value = TunnelManager.shared.accountNumber.map { string in - return formatAccountNumber(string) + return StringFormatter.formattedAccountNumber(from: string) } contentView.accountTokenRowView.actionHandler = { [weak self] in self?.copyAccountToken() @@ -384,7 +384,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb let workItem = DispatchWorkItem { [weak self] in guard let accountNumber = TunnelManager.shared.accountNumber else { return } - self?.contentView.accountTokenRowView.value = self?.formatAccountNumber(accountNumber) + self?.contentView.accountTokenRowView.value = StringFormatter.formattedAccountNumber(from: accountNumber) } copyToPasteboardWork?.cancel() @@ -443,10 +443,6 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb } } - private func formatAccountNumber(_ string: String) -> String { - return string.split(every: 4).joined(separator: " ") - } - } private extension REST.CreateApplePaymentResponse { diff --git a/ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/Contents.json new file mode 100644 index 0000000000..dc37d59299 --- /dev/null +++ b/ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "IconCloseSml.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/IconCloseSml.pdf b/ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/IconCloseSml.pdf Binary files differnew file mode 100644 index 0000000000..e552786d1c --- /dev/null +++ b/ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/IconCloseSml.pdf diff --git a/ios/MullvadVPN/LoginViewController.swift b/ios/MullvadVPN/LoginViewController.swift index b36f83dd8d..0c356fec68 100644 --- a/ios/MullvadVPN/LoginViewController.swift +++ b/ios/MullvadVPN/LoginViewController.swift @@ -83,6 +83,10 @@ class LoginViewController: UIViewController, RootContainment { } } + private var canBeginLogin: Bool { + return contentView.accountInputGroup.satisfiesMinimumTokenLengthRequirement + } + weak var delegate: LoginViewControllerDelegate? override var preferredStatusBarStyle: UIStatusBarStyle { @@ -107,24 +111,14 @@ class LoginViewController: UIViewController, RootContainment { contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor), contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) + updateLastUsedAccount() - contentView.accountInputGroup.onSendButton = { [weak self] _ in - guard let self = self else { return } - - if self.canBeginLogin() { - self.doLogin() - } - } + contentView.accountInputGroup.delegate = self contentView.accountInputGroup.setOnReturnKey { [weak self] _ in guard let self = self else { return true } - if self.canBeginLogin() { - self.doLogin() - return true - } else { - return false - } + return self.attemptLogin() } // There is no need to set the input accessory toolbar on iPad since it has a dedicated @@ -163,9 +157,10 @@ class LoginViewController: UIViewController, RootContainment { // MARK: - Public func reset() { - contentView.accountInputGroup.clearToken() + contentView.accountInputGroup.clearAccount() loginState = .default updateKeyboardToolbar() + updateLastUsedAccount() } // MARK: - UITextField notifications @@ -208,13 +203,13 @@ class LoginViewController: UIViewController, RootContainment { @objc func createNewAccount() { beginLogin(method: .newAccount) - contentView.accountInputGroup.clearToken() + contentView.accountInputGroup.clearAccount() updateKeyboardToolbar() self.delegate?.loginViewControllerLoginWithNewAccount(self, completion: { [weak self] completion in switch completion { case .success(let accountData): - self?.contentView.accountInputGroup.setToken(accountData?.number ?? "") + self?.contentView.accountInputGroup.setAccount(accountData?.number ?? "") self?.endLogin(.success(.newAccount)) case .failure(let error): self?.endLogin(.failure(error)) @@ -226,6 +221,16 @@ class LoginViewController: UIViewController, RootContainment { // MARK: - Private + private func updateLastUsedAccount() { + do { + let accountNumber = try SettingsManager.getLastUsedAccount() + contentView.accountInputGroup.setLastUsedAccount(StringFormatter.formattedAccountNumber(from: accountNumber)) + } catch { + logger.error(chainedError: AnyChainedError(error), + message: "Failed to update last used account.") + } + } + private func loginStateDidChange() { contentView.accountInputGroup.setLoginState(loginState, animated: true) @@ -282,7 +287,7 @@ class LoginViewController: UIViewController, RootContainment { } private func updateKeyboardToolbar() { - accountInputAccessoryLoginButton.isEnabled = canBeginLogin() + accountInputAccessoryLoginButton.isEnabled = canBeginLogin } private func updateCreateButtonEnabled() { @@ -306,8 +311,13 @@ class LoginViewController: UIViewController, RootContainment { contentView.createAccountButton.isEnabled = isEnabled } - private func canBeginLogin() -> Bool { - return contentView.accountInputGroup.satisfiesMinimumTokenLengthRequirement + @discardableResult private func attemptLogin() -> Bool { + if canBeginLogin { + doLogin() + return true + } else { + return false + } } } @@ -400,3 +410,22 @@ private extension LoginState { } } } + +// MARK: - AccountInputGroupViewDelegate + +extension LoginViewController: AccountInputGroupViewDelegate { + func accountInputGroupViewShouldRemoveLastUsedAccount(_ view: AccountInputGroupView) -> Bool { + do { + try SettingsManager.setLastUsedAccount(nil) + return true + } catch { + self.logger.error(chainedError: AnyChainedError(error), + message: "Failed to remove last used account.") + return false + } + } + + func accountInputGroupViewShouldAttemptLogin(_ view: AccountInputGroupView) { + attemptLogin() + } +} diff --git a/ios/MullvadVPN/StringFormatter.swift b/ios/MullvadVPN/StringFormatter.swift new file mode 100644 index 0000000000..1e4c2ae456 --- /dev/null +++ b/ios/MullvadVPN/StringFormatter.swift @@ -0,0 +1,15 @@ +// +// StringFormatter.swift +// MullvadVPN +// +// Created by Andreas Lif on 2022-06-10. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +struct StringFormatter { + static func formattedAccountNumber(from string: String) -> String { + return string.split(every: 4).joined(separator: " ") + } +} diff --git a/ios/MullvadVPN/UIColor+Palette.swift b/ios/MullvadVPN/UIColor+Palette.swift index 755b058cd0..dcb8c98e45 100644 --- a/ios/MullvadVPN/UIColor+Palette.swift +++ b/ios/MullvadVPN/UIColor+Palette.swift @@ -24,9 +24,9 @@ extension UIColor { } enum AuthenticatingState { - static let borderColor = UIColor.clear - static let textColor = UIColor.white - static let backgroundColor = UIColor.white.withAlphaComponent(0.2) + static let borderColor = secondaryColor + static let textColor = primaryColor + static let backgroundColor = UIColor.white.withAlphaComponent(0.4) } } diff --git a/ios/convert-assets.rb b/ios/convert-assets.rb index ac9584c1b2..5d8be56277 100755 --- a/ios/convert-assets.rb +++ b/ios/convert-assets.rb @@ -31,7 +31,8 @@ GRAPHICAL_ASSETS = [ "location-marker-secure.svg", "location-marker-unsecure.svg", "logo-icon.svg", - "logo-text.svg" + "logo-text.svg", + "icon-close-sml.svg" ] # App icon sizes |
