summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndreas Lif <andreas.lif@shortcut.io>2022-06-10 14:39:58 +0200
committerAndreas Lif <andreas.lif@shortcut.io>2022-06-15 15:17:29 +0200
commite4d79bd24eeb388520604364321cd9efff26c10c (patch)
treea0a8ec3d4d8b2f99a2d2a97198a222eeb50a4adb
parentb159c406634f6adbc309e00a57fa09299f94c037 (diff)
downloadmullvadvpn-e4d79bd24eeb388520604364321cd9efff26c10c.tar.xz
mullvadvpn-e4d79bd24eeb388520604364321cd9efff26c10c.zip
Add last used account to login view
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj8
-rw-r--r--ios/MullvadVPN/AccountInputGroupView.swift191
-rw-r--r--ios/MullvadVPN/AccountViewController.swift8
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/Contents.json15
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/IconCloseSml.pdfbin0 -> 1137 bytes
-rw-r--r--ios/MullvadVPN/LoginViewController.swift67
-rw-r--r--ios/MullvadVPN/StringFormatter.swift15
-rw-r--r--ios/MullvadVPN/UIColor+Palette.swift6
-rwxr-xr-xios/convert-assets.rb3
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
new file mode 100644
index 0000000000..e552786d1c
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/IconCloseSml.imageset/IconCloseSml.pdf
Binary files differ
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