diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2022-08-03 17:54:33 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2022-08-03 17:54:33 +0200 |
| commit | 6194006e59fb3c90a61981a18160ba50dddf4d73 (patch) | |
| tree | b8f2815d7a21e8fa3deb8ff4d73172bcc25e675e | |
| parent | b8b1c60c39d63de76062ba2292512b310cb0ff73 (diff) | |
| parent | 8f74ad954b034b50dce09e4e8d00249eb6bad065 (diff) | |
| download | mullvadvpn-6194006e59fb3c90a61981a18160ba50dddf4d73.tar.xz mullvadvpn-6194006e59fb3c90a61981a18160ba50dddf4d73.zip | |
Merge branch 'format-source-code'
186 files changed, 3401 insertions, 2145 deletions
diff --git a/ios/MullvadVPN/AccountContentView.swift b/ios/MullvadVPN/AccountContentView.swift index b357f3d067..b0ff256dbc 100644 --- a/ios/MullvadVPN/AccountContentView.swift +++ b/ios/MullvadVPN/AccountContentView.swift @@ -9,7 +9,6 @@ import UIKit class AccountContentView: UIView { - let purchaseButton: InAppPurchaseButton = { let button = InAppPurchaseButton() button.translatesAutoresizingMaskIntoConstraints = false @@ -60,7 +59,12 @@ class AccountContentView: UIView { }() lazy var contentStackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [accountDeviceRow, accountTokenRowView, accountExpiryRowView]) + let stackView = + UIStackView(arrangedSubviews: [ + accountDeviceRow, + accountTokenRowView, + accountExpiryRowView, + ]) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .vertical stackView.spacing = UIMetrics.sectionSpacing @@ -68,7 +72,8 @@ class AccountContentView: UIView { }() lazy var buttonStackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [purchaseButton, restorePurchasesButton, logoutButton]) + let stackView = + UIStackView(arrangedSubviews: [purchaseButton, restorePurchasesButton, logoutButton]) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .vertical stackView.spacing = UIMetrics.interButtonSpacing @@ -89,10 +94,13 @@ class AccountContentView: UIView { contentStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), contentStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - buttonStackView.topAnchor.constraint(greaterThanOrEqualTo: contentStackView.bottomAnchor, constant: UIMetrics.sectionSpacing), + buttonStackView.topAnchor.constraint( + greaterThanOrEqualTo: contentStackView.bottomAnchor, + constant: UIMetrics.sectionSpacing + ), buttonStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), buttonStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - buttonStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + buttonStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), ]) } @@ -102,7 +110,6 @@ class AccountContentView: UIView { } class AccountDeviceRow: UIView { - var deviceName: String? { didSet { deviceLabel.text = deviceName?.capitalized ?? "" @@ -146,7 +153,7 @@ class AccountDeviceRow: UIView { deviceLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8), deviceLabel.leadingAnchor.constraint(equalTo: leadingAnchor), deviceLabel.trailingAnchor.constraint(equalTo: trailingAnchor), - deviceLabel.bottomAnchor.constraint(equalTo: bottomAnchor) + deviceLabel.bottomAnchor.constraint(equalTo: bottomAnchor), ]) isAccessibilityElement = true @@ -237,7 +244,10 @@ class AccountNumberRow: UIView { copyButton.heightAnchor.constraint(equalTo: accountNumberLabel.heightAnchor), copyButton.centerYAnchor.constraint(equalTo: accountNumberLabel.centerYAnchor), - copyButton.leadingAnchor.constraint(equalTo: showHideButton.trailingAnchor, constant: 24), + copyButton.leadingAnchor.constraint( + equalTo: showHideButton.trailingAnchor, + constant: 24 + ), copyButton.trailingAnchor.constraint(equalTo: trailingAnchor), ]) @@ -298,7 +308,6 @@ class AccountNumberRow: UIView { } } - private var _accessibilityAttributedValue: NSAttributedString? { guard let accountNumber = accountNumber else { return nil @@ -330,7 +339,7 @@ class AccountNumberRow: UIView { UIAccessibilityCustomAction( name: showHideAccessibilityActionName, target: self, - selector: #selector(didTapShowHideAccount) + selector: #selector(didTapShowHideAccount) ), UIAccessibilityCustomAction( name: NSLocalizedString( @@ -340,8 +349,8 @@ class AccountNumberRow: UIView { comment: "" ), target: self, - selector: #selector(didTapCopyAccountNumber) - ) + selector: #selector(didTapCopyAccountNumber) + ), ] } @@ -368,12 +377,12 @@ class AccountNumberRow: UIView { let tickIcon = UIImage(named: "IconTick") copyButton.setImage(tickIcon, for: .normal) - copyButton.tintColor = .successColor + copyButton.tintColor = .successColor } else { let copyIcon = UIImage(named: "IconCopy") copyButton.setImage(copyIcon, for: .normal) - copyButton.tintColor = .white + copyButton.tintColor = .white } } @@ -405,12 +414,11 @@ class AccountNumberRow: UIView { } class AccountExpiryRow: UIView { - var value: Date? { didSet { let expiry = value - if let expiry = expiry, expiry <= Date() { + if let expiry = expiry, expiry <= Date() { let localizedString = NSLocalizedString( "ACCOUNT_OUT_OF_TIME_LABEL", tableName: "Account", @@ -480,7 +488,10 @@ class AccountExpiryRow: UIView { NSLayoutConstraint.activate([ textLabel.topAnchor.constraint(equalTo: topAnchor), textLabel.leadingAnchor.constraint(equalTo: leadingAnchor), - textLabel.trailingAnchor.constraint(greaterThanOrEqualTo: activityIndicator.leadingAnchor, constant: -8), + textLabel.trailingAnchor.constraint( + greaterThanOrEqualTo: activityIndicator.leadingAnchor, + constant: -8 + ), activityIndicator.topAnchor.constraint(equalTo: textLabel.topAnchor), activityIndicator.bottomAnchor.constraint(equalTo: textLabel.bottomAnchor), @@ -489,7 +500,7 @@ class AccountExpiryRow: UIView { valueLabel.topAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 8), valueLabel.leadingAnchor.constraint(equalTo: leadingAnchor), valueLabel.trailingAnchor.constraint(equalTo: trailingAnchor), - valueLabel.bottomAnchor.constraint(equalTo: bottomAnchor) + valueLabel.bottomAnchor.constraint(equalTo: bottomAnchor), ]) isAccessibilityElement = true diff --git a/ios/MullvadVPN/AccountInputGroupView.swift b/ios/MullvadVPN/AccountInputGroupView.swift index 71b7a6a52f..39ece2f367 100644 --- a/ios/MullvadVPN/AccountInputGroupView.swift +++ b/ios/MullvadVPN/AccountInputGroupView.swift @@ -6,8 +6,8 @@ // Copyright © 2019 Mullvad VPN AB. All rights reserved. // -import UIKit import Logging +import UIKit private let accountInputGroupViewAnimationDuration: TimeInterval = 0.25 @@ -17,7 +17,6 @@ protocol AccountInputGroupViewDelegate: AnyObject { } class AccountInputGroupView: UIView { - enum Style { case normal, error, authenticating } @@ -95,7 +94,7 @@ class AccountInputGroupView: UIView { return view }() - private var showsLastUsedAccountRow: Bool = false + private var showsLastUsedAccountRow = false private let lastUsedAccountButton: UIButton = { let button = UIButton(type: .system) @@ -147,8 +146,8 @@ class AccountInputGroupView: UIView { 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 @@ -208,9 +207,13 @@ class AccountInputGroupView: UIView { sendButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) lastUsedAccountButton.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) - removeLastUsedAccountButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + removeLastUsedAccountButton.setContentCompressionResistancePriority( + .defaultHigh, + for: .horizontal + ) - lastUsedAccountVisibleConstraint = heightAnchor.constraint(equalTo: contentView.heightAnchor) + lastUsedAccountVisibleConstraint = heightAnchor + .constraint(equalTo: contentView.heightAnchor) lastUsedAccountHiddenConstraint = heightAnchor.constraint(equalTo: topRowView.heightAnchor) NSLayoutConstraint.activate([ @@ -248,11 +251,14 @@ class AccountInputGroupView: UIView { lastUsedAccountButton.topAnchor.constraint(equalTo: bottomRowView.topAnchor), lastUsedAccountButton.bottomAnchor.constraint(equalTo: bottomRowView.bottomAnchor), lastUsedAccountButton.leadingAnchor.constraint(equalTo: bottomRowView.leadingAnchor), - lastUsedAccountButton.trailingAnchor.constraint(equalTo: removeLastUsedAccountButton.leadingAnchor), + lastUsedAccountButton.trailingAnchor + .constraint(equalTo: removeLastUsedAccountButton.leadingAnchor), removeLastUsedAccountButton.topAnchor.constraint(equalTo: bottomRowView.topAnchor), - removeLastUsedAccountButton.bottomAnchor.constraint(equalTo: bottomRowView.bottomAnchor), - removeLastUsedAccountButton.trailingAnchor.constraint(equalTo: bottomRowView.trailingAnchor), + removeLastUsedAccountButton.bottomAnchor + .constraint(equalTo: bottomRowView.bottomAnchor), + removeLastUsedAccountButton.trailingAnchor + .constraint(equalTo: bottomRowView.trailingAnchor), removeLastUsedAccountButton.widthAnchor.constraint(equalTo: sendButton.widthAnchor), ]) @@ -268,9 +274,17 @@ class AccountInputGroupView: UIView { updateSendButtonAppearance(animated: false) updateKeyboardReturnKeyEnabled() - lastUsedAccountButton.addTarget(self, action: #selector(didTapLastUsedAccount), for: .touchUpInside) + lastUsedAccountButton.addTarget( + self, + action: #selector(didTapLastUsedAccount), + for: .touchUpInside + ) - removeLastUsedAccountButton.addTarget(self, action: #selector(didTapRemoveLastUsedAccount), for: .touchUpInside) + removeLastUsedAccountButton.addTarget( + self, + action: #selector(didTapRemoveLastUsedAccount), + for: .touchUpInside + ) addTextFieldNotificationObservers() addAccessibilityNotificationObservers() @@ -403,18 +417,24 @@ class AccountInputGroupView: UIView { private func addTextFieldNotificationObservers() { let notificationCenter = NotificationCenter.default - notificationCenter.addObserver(self, - selector: #selector(textDidBeginEditing), - name: UITextField.textDidBeginEditingNotification, - object: privateTextField) - notificationCenter.addObserver(self, - selector: #selector(textDidChange), - name: UITextField.textDidChangeNotification, - object: privateTextField) - notificationCenter.addObserver(self, - selector: #selector(textDidEndEditing), - name: UITextField.textDidEndEditingNotification, - object: privateTextField) + notificationCenter.addObserver( + self, + selector: #selector(textDidBeginEditing), + name: UITextField.textDidBeginEditingNotification, + object: privateTextField + ) + notificationCenter.addObserver( + self, + selector: #selector(textDidChange), + name: UITextField.textDidChangeNotification, + object: privateTextField + ) + notificationCenter.addObserver( + self, + selector: #selector(textDidEndEditing), + name: UITextField.textDidEndEditingNotification, + object: privateTextField + ) } private func updateAppearance() { @@ -485,7 +505,8 @@ class AccountInputGroupView: UIView { bottomRowView.accessibilityElementsHidden = !shouldShow if lastUsedAccountButton.accessibilityElementIsFocused() || - removeLastUsedAccountButton.accessibilityElementIsFocused() { + removeLastUsedAccountButton.accessibilityElementIsFocused() + { UIAccessibility.post(notification: .layoutChanged, argument: textField) } } @@ -529,7 +550,10 @@ class AccountInputGroupView: UIView { } private func borderBezierPath(size: CGSize) -> UIBezierPath { - let borderPath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: borderRadius) + let borderPath = UIBezierPath( + roundedRect: CGRect(origin: .zero, size: size), + cornerRadius: borderRadius + ) borderPath.lineWidth = borderWidth return borderPath @@ -537,7 +561,7 @@ class AccountInputGroupView: UIView { private func backgroundMaskImage(borderPath: UIBezierPath) -> UIImage { let renderer = UIGraphicsImageRenderer(bounds: borderPath.bounds) - return renderer.image { (ctx) in + return renderer.image { ctx in borderPath.fill() // strip out any overlapping pixels between the border and the background @@ -548,13 +572,17 @@ class AccountInputGroupView: UIView { // MARK: - Accessibility private func addAccessibilityNotificationObservers() { - NotificationCenter.default.addObserver(self, selector: #selector(voiceOverStatusDidChange(_:)), name: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(voiceOverStatusDidChange(_:)), + name: UIAccessibility.voiceOverStatusDidChangeNotification, + object: nil + ) } @objc private func voiceOverStatusDidChange(_ notification: Notification) { updateSendButtonAppearance(animated: true) } - } private class AccountInputBorderLayer: CAShapeLayer { diff --git a/ios/MullvadVPN/AccountTextField.swift b/ios/MullvadVPN/AccountTextField.swift index 8a4016ed73..74c6c1c45e 100644 --- a/ios/MullvadVPN/AccountTextField.swift +++ b/ios/MullvadVPN/AccountTextField.swift @@ -9,7 +9,6 @@ import UIKit class AccountTextField: CustomTextField, UITextFieldDelegate { - private let input = AccountTokenInput() var onReturnKey: ((AccountTextField) -> Bool)? @@ -49,7 +48,7 @@ class AccountTextField: CustomTextField, UITextFieldDelegate { return input.parsedString } - var enableReturnKey: Bool = true { + var enableReturnKey = true { didSet { updateKeyboardReturnKey() } @@ -66,8 +65,16 @@ class AccountTextField: CustomTextField, UITextFieldDelegate { // MARK: - UITextFieldDelegate - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - return input.textField(textField, shouldChangeCharactersIn: range, replacementString: string) + func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + return input.textField( + textField, + shouldChangeCharactersIn: range, + replacementString: string + ) } func textFieldShouldReturn(_ textField: UITextField) -> Bool { @@ -77,7 +84,7 @@ class AccountTextField: CustomTextField, UITextFieldDelegate { // MARK: - Notifications @objc private func keyboardWillShow(_ notification: Notification) { - if self.isFirstResponder { + if isFirstResponder { updateKeyboardReturnKey() } } @@ -90,7 +97,7 @@ class AccountTextField: CustomTextField, UITextFieldDelegate { private func setEnableKeyboardReturnKey(_ enableReturnKey: Bool) { let selector = NSSelectorFromString("setReturnKeyEnabled:") - if let inputDelegate = self.inputDelegate as? NSObject, inputDelegate.responds(to: selector) { + if let inputDelegate = inputDelegate as? NSObject, inputDelegate.responds(to: selector) { inputDelegate.setValue(enableReturnKey, forKey: "returnKeyEnabled") } } @@ -102,12 +109,11 @@ class AccountTextField: CustomTextField, UITextFieldDelegate { super.accessibilityValue = newValue } get { - if self.text?.isEmpty ?? true { + if text?.isEmpty ?? true { return "" } else { return super.accessibilityValue } } } - } diff --git a/ios/MullvadVPN/AccountTokenInput.swift b/ios/MullvadVPN/AccountTokenInput.swift index 317f7717a9..cccc5a1a75 100644 --- a/ios/MullvadVPN/AccountTokenInput.swift +++ b/ios/MullvadVPN/AccountTokenInput.swift @@ -11,8 +11,7 @@ import UIKit /// A class describing the account token input and caret management. /// Suitable to be used with `UITextField`. -class AccountTokenInput: NSObject -{ +class AccountTokenInput: NSObject { /// The group separator character static let groupSeparator: Character = " " @@ -41,7 +40,8 @@ class AccountTokenInput: NSObject replaceCharacters( in: stringRange, replacementString: replacementString, - emptySelection: false) + emptySelection: false + ) } /// Replace characters in range maintaining the caret position @@ -52,15 +52,16 @@ class AccountTokenInput: NSObject /// This is normally the default state unless a text range is /// selected. /// - func replaceCharacters(in range: Range<String.Index>, - replacementString: String, - emptySelection: Bool) - { + func replaceCharacters( + in range: Range<String.Index>, + replacementString: String, + emptySelection: Bool + ) { var stringRange = range // Since removing separator alone makes no sense, this computation extends the string range // to include the digit preceding a separator. - if replacementString.isEmpty && emptySelection && !formattedString.isEmpty { + if replacementString.isEmpty, emptySelection, !formattedString.isEmpty { let precedingDigitIndex = formattedString .prefix(through: stringRange.lowerBound) .lastIndex { Self.isDigit($0) } ?? formattedString.startIndex @@ -69,8 +70,10 @@ class AccountTokenInput: NSObject } // Replace the given range within a formatted string - let newString = formattedString.replacingCharacters(in: stringRange, - with: replacementString) + let newString = formattedString.replacingCharacters( + in: stringRange, + with: replacementString + ) // Number of digits within a string var numDigits = 0 @@ -102,7 +105,7 @@ class AccountTokenInput: NSObject } // Add separator between the groups of digits - if numDigits > 0 && numDigits % Self.groupSize == 0 { + if numDigits > 0, numDigits % Self.groupSize == 0 { reformattedString.append(Self.groupSeparator) if originalCaretPosition > index { @@ -124,17 +127,15 @@ class AccountTokenInput: NSObject private class func isDigit(_ character: Character) -> Bool { switch character { - case "0"..."9": + case "0" ... "9": return true default: return false } } - } extension AccountTokenInput: UITextFieldDelegate, UITextPasteDelegate { - /// Update the text and caret position in the given text field func updateTextField(_ textField: UITextField) { updateTextField(textField, notifyDelegate: false) @@ -142,14 +143,19 @@ extension AccountTokenInput: UITextFieldDelegate, UITextPasteDelegate { // MARK: - UITextFieldDelegate - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { let emptySelection = textField.selectedTextRange?.isEmpty ?? false let stringRange = Range(range, in: formattedString)! replaceCharacters( in: stringRange, replacementString: string, - emptySelection: emptySelection) + emptySelection: emptySelection + ) updateTextField(textField, notifyDelegate: true) @@ -161,8 +167,8 @@ extension AccountTokenInput: UITextFieldDelegate, UITextPasteDelegate { func textPasteConfigurationSupporting( _ textPasteConfigurationSupporting: UITextPasteConfigurationSupporting, performPasteOf attributedString: NSAttributedString, - to textRange: UITextRange) -> UITextRange - { + to textRange: UITextRange + ) -> UITextRange { guard let textField = textPasteConfigurationSupporting as? UITextField else { return textRange } @@ -176,7 +182,8 @@ extension AccountTokenInput: UITextFieldDelegate, UITextPasteDelegate { replaceCharacters( in: stringRange, replacementString: attributedString.string, - emptySelection: textRange.isEmpty) + emptySelection: textRange.isEmpty + ) updateTextField(textField, notifyDelegate: true) return caretTextRange(in: textField)! @@ -196,7 +203,8 @@ extension AccountTokenInput: UITextFieldDelegate, UITextPasteDelegate { private func caretTextRange(in textField: UITextField) -> UITextRange? { guard let position = textField.position( from: textField.beginningOfDocument, - offset: caretPositionUtf16) else { return nil } + offset: caretPositionUtf16 + ) else { return nil } return textField.textRange(from: position, to: position) } @@ -213,10 +221,10 @@ extension AccountTokenInput: UITextFieldDelegate, UITextPasteDelegate { } /// Post `UITextField.textDidChange` notification - class private func notifyTextDidChange(in textField: UITextField) { + private class func notifyTextDidChange(in textField: UITextField) { NotificationCenter.default.post( name: UITextField.textDidChangeNotification, - object: textField) + object: textField + ) } } - diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift index 579ca685fd..a755ef2239 100644 --- a/ios/MullvadVPN/AccountViewController.swift +++ b/ios/MullvadVPN/AccountViewController.swift @@ -6,9 +6,9 @@ // Copyright © 2019 Mullvad VPN AB. All rights reserved. // +import Logging import StoreKit import UIKit -import Logging protocol AccountViewControllerDelegate: AnyObject { func accountViewControllerDidLogout(_ controller: AccountViewController) @@ -29,21 +29,22 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb weak var delegate: AccountViewControllerDelegate? private lazy var purchaseButtonInteractionRestriction = - UserInterfaceInteractionRestriction { [weak self] (enableUserInteraction, _) in + UserInterfaceInteractionRestriction { [weak self] enableUserInteraction, _ in // Make sure to disable the button if the product is not loaded self?.contentView.purchaseButton.isEnabled = enableUserInteraction && self?.product != nil && AppStorePaymentManager.canMakePayments - } + } private lazy var viewControllerInteractionRestriction = - UserInterfaceInteractionRestriction { [weak self] (enableUserInteraction, animated) in + UserInterfaceInteractionRestriction { [weak self] enableUserInteraction, animated in self?.setEnableUserInteraction(enableUserInteraction, animated: true) - } + } private lazy var compoundInteractionRestriction = CompoundUserInterfaceInteractionRestriction(restrictions: [ - purchaseButtonInteractionRestriction, viewControllerInteractionRestriction]) + purchaseButtonInteractionRestriction, viewControllerInteractionRestriction, + ]) private var product: SKProduct? @@ -70,7 +71,8 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), contentView.topAnchor.constraint(equalTo: scrollView.topAnchor), - contentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.bottomAnchor), + contentView.bottomAnchor + .constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.bottomAnchor), contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), @@ -87,8 +89,16 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb self?.copyAccountToken() } - contentView.restorePurchasesButton.addTarget(self, action: #selector(restorePurchases), for: .touchUpInside) - contentView.purchaseButton.addTarget(self, action: #selector(doPurchase), for: .touchUpInside) + contentView.restorePurchasesButton.addTarget( + self, + action: #selector(restorePurchases), + for: .touchUpInside + ) + contentView.purchaseButton.addTarget( + self, + action: #selector(doPurchase), + for: .touchUpInside + ) contentView.logoutButton.addTarget(self, action: #selector(doLogout), for: .touchUpInside) AppStorePaymentManager.shared.addPaymentObserver(self) @@ -107,7 +117,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb // MARK: - Private methods private func updateView(from deviceState: DeviceState?) { - guard case .loggedIn(let accountData, let deviceData) = deviceState else { + guard case let .loggedIn(accountData, deviceData) = deviceState else { return } @@ -124,25 +134,26 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb purchaseButtonInteractionRestriction.increase(animated: true) - _ = AppStorePaymentManager.shared.requestProducts(with: [inAppPurchase]) { [weak self] completion in - guard let self = self else { return } + _ = AppStorePaymentManager.shared + .requestProducts(with: [inAppPurchase]) { [weak self] completion in + guard let self = self else { return } - switch completion { - case .success(let response): - if let product = response.products.first { - self.setProduct(product, animated: true) - } + switch completion { + case let .success(response): + if let product = response.products.first { + self.setProduct(product, animated: true) + } - case .failure(let error): - self.didFailLoadingProducts(with: error) + case let .failure(error): + self.didFailLoadingProducts(with: error) - case .cancelled: - break - } + case .cancelled: + break + } - self.contentView.purchaseButton.isLoading = false - self.purchaseButtonInteractionRestriction.decrease(animated: true) - } + self.contentView.purchaseButton.isLoading = false + self.purchaseButtonInteractionRestriction.decrease(animated: true) + } } private func setProduct(_ product: SKProduct, animated: Bool) { @@ -187,7 +198,7 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb private func setEnableUserInteraction(_ enableUserInteraction: Bool, animated: Bool) { // Disable all buttons - [contentView.restorePurchasesButton, contentView.logoutButton].forEach { (button) in + [contentView.restorePurchasesButton, contentView.logoutButton].forEach { button in button?.isEnabled = enableUserInteraction } @@ -214,8 +225,8 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb private func showTimeAddedConfirmationAlert( with response: REST.CreateApplePaymentResponse, - context: REST.CreateApplePaymentResponse.Context) - { + context: REST.CreateApplePaymentResponse.Context + ) { let alertController = UIAlertController( title: response.alertTitle(context: context), message: response.alertMessage(context: context), @@ -262,9 +273,10 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb comment: "" ), style: .cancel, - handler: { (alertAction) in + handler: { alertAction in completion(false) - }) + } + ) ) alertController.addAction( @@ -276,9 +288,10 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb comment: "" ), style: .destructive, - handler: { (alertAction) in + handler: { alertAction in completion(true) - }) + } + ) ) alertPresenter.enqueue(alertController, presentingController: self) @@ -323,7 +336,10 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb // no-op } - func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2) { + func tunnelManager( + _ manager: TunnelManager, + didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2 + ) { // no-op } @@ -333,7 +349,13 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb // MARK: - AppStorePaymentObserver - func appStorePaymentManager(_ manager: AppStorePaymentManager, transaction: SKPaymentTransaction?, payment: SKPayment, accountToken: String?, didFailWithError error: AppStorePaymentManager.Error) { + func appStorePaymentManager( + _ manager: AppStorePaymentManager, + transaction: SKPaymentTransaction?, + payment: SKPayment, + accountToken: String?, + didFailWithError error: AppStorePaymentManager.Error + ) { let alertController = UIAlertController( title: NSLocalizedString( "CANNOT_COMPLETE_PURCHASE_ALERT_TITLE", @@ -352,7 +374,8 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb tableName: "Account", value: "OK", comment: "" - ), style: .cancel) + ), style: .cancel + ) ) alertPresenter.enqueue(alertController, presentingController: self) @@ -362,7 +385,12 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb } } - func appStorePaymentManager(_ manager: AppStorePaymentManager, transaction: SKPaymentTransaction, accountToken: String, didFinishWithResponse response: REST.CreateApplePaymentResponse) { + func appStorePaymentManager( + _ manager: AppStorePaymentManager, + transaction: SKPaymentTransaction, + accountToken: String, + didFinishWithResponse response: REST.CreateApplePaymentResponse + ) { showTimeAddedConfirmationAlert(with: response, context: .purchase) if transaction.payment == pendingPayment { @@ -370,7 +398,6 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb } } - // MARK: - Actions @objc private func doLogout() { @@ -391,7 +418,8 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb @objc private func doPurchase() { guard let accountData = TunnelManager.shared.deviceState.accountData, - let product = product else { + let product = product + else { return } @@ -412,10 +440,10 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb _ = AppStorePaymentManager.shared.restorePurchases(for: accountData.number) { completion in switch completion { - case .success(let response): + case let .success(response): self.showTimeAddedConfirmationAlert(with: response, context: .restoration) - case .failure(let error): + case let .failure(error): let alertController = UIAlertController( title: NSLocalizedString( "RESTORE_PURCHASES_FAILURE_ALERT_TITLE", @@ -443,11 +471,9 @@ class AccountViewController: UIViewController, AppStorePaymentObserver, TunnelOb self.compoundInteractionRestriction.decrease(animated: true) } } - } private extension REST.CreateApplePaymentResponse { - enum Context { case purchase case restoration diff --git a/ios/MullvadVPN/AddressCache/AddressCacheStore.swift b/ios/MullvadVPN/AddressCache/AddressCacheStore.swift index 2addeb2754..94ea5d5850 100644 --- a/ios/MullvadVPN/AddressCache/AddressCacheStore.swift +++ b/ios/MullvadVPN/AddressCache/AddressCacheStore.swift @@ -10,7 +10,6 @@ import Foundation import Logging extension AddressCache { - struct CachedAddresses: Codable { /// Date when the cached addresses were last updated. var updatedAt: Date @@ -50,12 +49,20 @@ extension AddressCache { } class Store { - static let shared: Store = { let cacheFilename = "api-ip-address.json" - let cacheDirectoryURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! - let cacheFileURL = cacheDirectoryURL.appendingPathComponent(cacheFilename, isDirectory: false) - let prebundledCacheFileURL = Bundle.main.url(forResource: cacheFilename, withExtension: nil)! + let cacheDirectoryURL = FileManager.default.urls( + for: .applicationSupportDirectory, + in: .userDomainMask + ).first! + let cacheFileURL = cacheDirectoryURL.appendingPathComponent( + cacheFilename, + isDirectory: false + ) + let prebundledCacheFileURL = Bundle.main.url( + forResource: cacheFilename, + withExtension: nil + )! return Store( cacheFileURL: cacheFileURL, @@ -67,7 +74,7 @@ extension AddressCache { return CachedAddresses( updatedAt: Date(timeIntervalSince1970: 0), endpoints: [ - ApplicationConfiguration.defaultAPIEndpoint + ApplicationConfiguration.defaultAPIEndpoint, ] ) } @@ -154,7 +161,10 @@ extension AddressCache { currentEndpoint = cachedAddresses.endpoints.first! - logger.debug("Failed to communicate using \(failedEndpoint). Next endpoint: \(currentEndpoint)") + logger + .debug( + "Failed to communicate using \(failedEndpoint). Next endpoint: \(currentEndpoint)" + ) do { try writeToDisk() @@ -216,8 +226,7 @@ extension AddressCache { cacheFileURL: URL, prebundledCacheFileURL: URL, logger: Logger - ) throws -> ReadResult - { + ) throws -> ReadResult { do { let readResult = ReadResult( cachedAddresses: try readFromCacheLocation(cacheFileURL), @@ -265,7 +274,9 @@ extension AddressCache { return try JSONDecoder().decode(CachedAddresses.self, from: data) } - private static func readFromBundle(_ prebundledCacheFileURL: URL) throws -> CachedAddresses { + private static func readFromBundle(_ prebundledCacheFileURL: URL) throws + -> CachedAddresses + { let data = try Data(contentsOf: prebundledCacheFileURL) let endpoints = try JSONDecoder().decode([AnyIPEndpoint].self, from: data) @@ -287,6 +298,5 @@ extension AddressCache { let data = try JSONEncoder().encode(cachedAddresses) try data.write(to: cacheFileURL, options: .atomic) } - } } diff --git a/ios/MullvadVPN/AddressCache/AddressCacheTracker.swift b/ios/MullvadVPN/AddressCache/AddressCacheTracker.swift index 430212c24d..69300772ac 100644 --- a/ios/MullvadVPN/AddressCache/AddressCacheTracker.swift +++ b/ios/MullvadVPN/AddressCache/AddressCacheTracker.swift @@ -6,19 +6,16 @@ // Copyright © 2021 Mullvad VPN AB. All rights reserved. // -import UIKit import Logging +import UIKit extension AddressCache { - class Tracker { /// Shared instance. - static let shared: AddressCache.Tracker = { - return AddressCache.Tracker( - apiProxy: REST.ProxyFactory.shared.createAPIProxy(), - store: AddressCache.Store.shared - ) - }() + static let shared = AddressCache.Tracker( + apiProxy: REST.ProxyFactory.shared.createAPIProxy(), + store: AddressCache.Store.shared + ) /// Update interval (in seconds). private static let updateInterval: TimeInterval = 60 * 60 * 24 @@ -95,8 +92,7 @@ extension AddressCache { func updateEndpoints( completionHandler: ((OperationCompletion<Bool, Error>) -> Void)? = nil - ) -> Cancellable - { + ) -> Cancellable { let operation = ResultBlockOperation<Bool, Error> { operation in guard self.nextScheduleDate() <= Date() else { operation.finish(completion: .success(false)) @@ -142,10 +138,10 @@ extension AddressCache { defer { nslock.unlock() } switch completion { - case .success(let endpoints): + case let .success(endpoints): store.setEndpoints(endpoints) - case .failure(let error): + case let .failure(error): logger.error( chainedError: AnyChainedError(error), message: "Failed to update address cache." @@ -180,7 +176,8 @@ extension AddressCache { let scheduleDate = self._nextScheduleDate() - self.logger.debug("Schedule next address cache update at \(scheduleDate.logFormatDate()).") + self.logger + .debug("Schedule next address cache update at \(scheduleDate.logFormatDate()).") self.scheduleEndpointsUpdate(startTime: .now() + scheduleDate.timeIntervalSinceNow) } @@ -199,6 +196,5 @@ extension AddressCache { return max(nextDate, Date()) } - } } diff --git a/ios/MullvadVPN/AlertPresenter.swift b/ios/MullvadVPN/AlertPresenter.swift index f67b07a3e8..c260201e05 100644 --- a/ios/MullvadVPN/AlertPresenter.swift +++ b/ios/MullvadVPN/AlertPresenter.swift @@ -10,7 +10,8 @@ import Foundation import UIKit class AlertPresenter { - static let alertControllerDidDismissNotification = Notification.Name("UIAlertControllerDidDismiss") + static let alertControllerDidDismissNotification = Notification + .Name("UIAlertControllerDidDismiss") private let operationQueue: OperationQueue = { let operationQueue = AsyncOperationQueue() @@ -23,14 +24,22 @@ class AlertPresenter { /// Swizzle `viewDidDisappear` on `UIAlertController` in order to be able to /// detect when the controller disappears. /// The event is broadcasted via `AlertPresenter.alertControllerDidDismissNotification` notification. - swizzleMethod(aClass: UIAlertController.self, originalSelector: #selector(UIAlertController.viewDidDisappear(_:)), newSelector: #selector(UIAlertController.alertPresenter_viewDidDisappear(_:))) + swizzleMethod( + aClass: UIAlertController.self, + originalSelector: #selector(UIAlertController.viewDidDisappear(_:)), + newSelector: #selector(UIAlertController.alertPresenter_viewDidDisappear(_:)) + ) }() init() { _ = Self.initClass } - func enqueue(_ alertController: UIAlertController, presentingController: UIViewController, presentCompletion: (() -> Void)? = nil) { + func enqueue( + _ alertController: UIAlertController, + presentingController: UIViewController, + presentCompletion: (() -> Void)? = nil + ) { let operation = PresentAlertOperation( alertController: alertController, presentingController: presentingController, @@ -43,16 +52,18 @@ class AlertPresenter { func cancelAll() { operationQueue.cancelAllOperations() } - } -fileprivate extension UIAlertController { +private extension UIAlertController { @objc dynamic func alertPresenter_viewDidDisappear(_ animated: Bool) { // Call super implementation alertPresenter_viewDidDisappear(animated) if presentingViewController == nil { - NotificationCenter.default.post(name: AlertPresenter.alertControllerDidDismissNotification, object: self) + NotificationCenter.default.post( + name: AlertPresenter.alertControllerDidDismissNotification, + object: self + ) } } } diff --git a/ios/MullvadVPN/AnyIPAddress.swift b/ios/MullvadVPN/AnyIPAddress.swift index 1898754a69..7916fe3a78 100644 --- a/ios/MullvadVPN/AnyIPAddress.swift +++ b/ios/MullvadVPN/AnyIPAddress.swift @@ -20,9 +20,9 @@ enum AnyIPAddress: IPAddress, Codable, Equatable, CustomDebugStringConvertible { private var innerAddress: IPAddress { switch self { - case .ipv4(let ipv4Address): + case let .ipv4(ipv4Address): return ipv4Address - case .ipv6(let ipv6Address): + case let .ipv6(ipv6Address): return ipv6Address } } @@ -39,7 +39,11 @@ enum AnyIPAddress: IPAddress, Codable, Equatable, CustomDebugStringConvertible { } else if container.contains(.ipv6) { self = .ipv6(try container.decode(IPv6Address.self, forKey: .ipv6)) } else { - throw DecodingError.dataCorruptedError(forKey: .ipv4, in: container, debugDescription: "Invalid AnyIPAddress representation") + throw DecodingError.dataCorruptedError( + forKey: .ipv4, + in: container, + debugDescription: "Invalid AnyIPAddress representation" + ) } } @@ -47,9 +51,9 @@ enum AnyIPAddress: IPAddress, Codable, Equatable, CustomDebugStringConvertible { var container = encoder.container(keyedBy: CodingKeys.self) switch self { - case .ipv4(let ipv4Address): + case let .ipv4(ipv4Address): try container.encode(ipv4Address, forKey: .ipv4) - case .ipv6(let ipv6Address): + case let .ipv6(ipv6Address): try container.encode(ipv6Address, forKey: .ipv6) } } @@ -92,9 +96,9 @@ enum AnyIPAddress: IPAddress, Codable, Equatable, CustomDebugStringConvertible { var debugDescription: String { switch self { - case .ipv4(let ipv4Address): + case let .ipv4(ipv4Address): return "\(ipv4Address)" - case .ipv6(let ipv6Address): + case let .ipv6(ipv6Address): return "\(ipv6Address)" } } diff --git a/ios/MullvadVPN/AppButton.swift b/ios/MullvadVPN/AppButton.swift index e6ff62a07f..7704fbe49f 100644 --- a/ios/MullvadVPN/AppButton.swift +++ b/ios/MullvadVPN/AppButton.swift @@ -51,7 +51,6 @@ private extension UIControl.State { /// A subclass that implements the button that visually look like URL links on the web class LinkButton: CustomButton { - override init(frame: CGRect) { super.init(frame: frame) commonInit() @@ -77,15 +76,18 @@ class LinkButton: CustomButton { private func updateAttributedTitle(string: String?) { let states: [UIControl.State] = [.normal, .highlighted, .disabled] - states.forEach { (state) in + states.forEach { state in let attributedTitle = string.flatMap { makeAttributedTitle($0, for: state) } self.setAttributedTitle(attributedTitle, for: state) } } - private func makeAttributedTitle(_ title: String, for state: UIControl.State) -> NSAttributedString { + private func makeAttributedTitle( + _ title: String, + for state: UIControl.State + ) -> NSAttributedString { var attributes: [NSAttributedString.Key: Any] = [ - .underlineStyle: NSUnderlineStyle.single.rawValue + .underlineStyle: NSUnderlineStyle.single.rawValue, ] if let titleColor = state.customButtonTitleColor { @@ -98,7 +100,6 @@ class LinkButton: CustomButton { /// A subclass that implements action buttons used across the app class AppButton: CustomButton { - var defaultContentInsets: UIEdgeInsets { switch traitCollection.userInterfaceIdiom { case .phone: @@ -132,9 +133,11 @@ class AppButton: CustomButton { case .translucentNeutral: return UIImage(named: "TranslucentNeutralButton") case .translucentDangerSplitLeft: - return UIImage(named: "TranslucentDangerSplitLeftButton")?.imageFlippedForRightToLeftLayoutDirection() + return UIImage(named: "TranslucentDangerSplitLeftButton")? + .imageFlippedForRightToLeftLayoutDirection() case .translucentDangerSplitRight: - return UIImage(named: "TranslucentDangerSplitRightButton")?.imageFlippedForRightToLeftLayoutDirection() + return UIImage(named: "TranslucentDangerSplitRightButton")? + .imageFlippedForRightToLeftLayoutDirection() } } } @@ -167,19 +170,19 @@ class AppButton: CustomButton { var contentInsets = contentEdgeInsets if contentInsets.top == 0 { - contentInsets.top = self.defaultContentInsets.top + contentInsets.top = defaultContentInsets.top } if contentInsets.bottom == 0 { - contentInsets.bottom = self.defaultContentInsets.bottom + contentInsets.bottom = defaultContentInsets.bottom } if contentInsets.right == 0 { - contentInsets.right = self.defaultContentInsets.right + contentInsets.right = defaultContentInsets.right } if contentInsets.left == 0 { - contentInsets.left = self.defaultContentInsets.left + contentInsets.left = defaultContentInsets.left } contentEdgeInsets = contentInsets @@ -188,7 +191,7 @@ class AppButton: CustomButton { titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold) let states: [UIControl.State] = [.normal, .highlighted, .disabled] - states.forEach { (state) in + states.forEach { state in if let titleColor = state.customButtonTitleColor { setTitleColor(titleColor, for: state) } @@ -207,16 +210,16 @@ class AppButton: CustomButton { override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - if traitCollection.userInterfaceIdiom != previousTraitCollection?.userInterfaceIdiom, !overrideContentEdgeInsets { - contentEdgeInsets = self.defaultContentInsets + if traitCollection.userInterfaceIdiom != previousTraitCollection?.userInterfaceIdiom, + !overrideContentEdgeInsets + { + contentEdgeInsets = defaultContentInsets } } - } /// A custom `UIButton` subclass that implements additional layouts for the image class CustomButton: UIButton { - var imageAlignment: ButtonImageAlignment = .leading { didSet { invalidateIntrinsicContentSize() @@ -292,7 +295,7 @@ class CustomButton: UIButton { } else { // Fix: on iOS 12 the image view frame is not always set, even though the `UIButton` // calls `imageRect` to compute the image layout frame. - let imageRect = self.imageRect(forContentRect: contentRect(forBounds: bounds)) + let imageRect = imageRect(forContentRect: contentRect(forBounds: bounds)) if imageView?.frame != imageRect { imageView?.frame = imageRect } @@ -368,5 +371,4 @@ class CustomButton: UIButton { override func titleRect(forContentRect contentRect: CGRect) -> CGRect { return computeLayout(forContentRect: contentRect).0 } - } diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 6908f86a43..39a669b3fd 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -6,19 +6,19 @@ // Copyright © 2019 Mullvad VPN AB. All rights reserved. // -import UIKit import BackgroundTasks -import StoreKit -import UserNotifications import Intents import Logging +import StoreKit +import UIKit +import UserNotifications @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { private var logger: Logger! #if targetEnvironment(simulator) - private let simulatorTunnelProvider = SimulatorTunnelProviderHost() + private let simulatorTunnelProvider = SimulatorTunnelProviderHost() #endif private let operationQueue: AsyncOperationQueue = { @@ -32,14 +32,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - Application lifecycle - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { initLoggingSystem(bundleIdentifier: Bundle.main.bundleIdentifier!) logger = Logger(label: "AppDelegate") #if targetEnvironment(simulator) - // Configure mock tunnel provider on simulator - SimulatorTunnelProvider.shared.delegate = simulatorTunnelProvider + // Configure mock tunnel provider on simulator + SimulatorTunnelProvider.shared.delegate = simulatorTunnelProvider #endif if #available(iOS 13.0, *) { @@ -96,9 +99,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application( _ application: UIApplication, - performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void - ) - { + performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) + -> Void + ) { logger.debug("Start background refresh.") let updateAddressCacheOperation = ResultBlockOperation<Bool, Error> { operation in @@ -122,8 +125,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - let rotatePrivateKeyOperation = ResultBlockOperation<Bool, Error> - { operation in + let rotatePrivateKeyOperation = ResultBlockOperation<Bool, Error> { operation in let handle = TunnelManager.shared.rotatePrivateKey(forceRotate: false) { completion in operation.finish(completion: completion) } @@ -134,13 +136,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } rotatePrivateKeyOperation.addDependencies([ updateRelaysOperation, - updateAddressCacheOperation + updateAddressCacheOperation, ]) let operations = [ updateAddressCacheOperation, updateRelaysOperation, - rotatePrivateKeyOperation + rotatePrivateKeyOperation, ] let completeOperation = TransformOperation<UIBackgroundFetchResult, Void, Never>( @@ -377,7 +379,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private func setupNotificationHandler() { NotificationManager.shared.notificationProviders = [ AccountExpiryNotificationProvider(), - TunnelErrorNotificationProvider() + TunnelErrorNotificationProvider(), ] UNUserNotificationCenter.current().delegate = self } @@ -389,8 +391,7 @@ extension AppDelegate: AppStorePaymentManagerDelegate { func appStorePaymentManager( _ manager: AppStorePaymentManager, didRequestAccountTokenFor payment: SKPayment - ) -> String? - { + ) -> String? { // Since we do not persist the relation between payment and account number between the // app launches, we assume that all successful purchases belong to the active account // number. @@ -401,10 +402,15 @@ extension AppDelegate: AppStorePaymentManagerDelegate { // MARK: - UNUserNotificationCenterDelegate extension AppDelegate: UNUserNotificationCenterDelegate { - func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { let blockOperation = AsyncBlockOperation(dispatchQueue: .main) { if response.notification.request.identifier == accountExpiryNotificationIdentifier, - response.actionIdentifier == UNNotificationDefaultActionIdentifier { + response.actionIdentifier == UNNotificationDefaultActionIdentifier + { if #available(iOS 13.0, *) { let sceneDelegate = UIApplication.shared.connectedScenes .first?.delegate as? SceneDelegate @@ -421,7 +427,12 @@ extension AppDelegate: UNUserNotificationCenterDelegate { operationQueue.addOperation(blockOperation) } - func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) + -> Void + ) { if #available(iOS 14.0, *) { completionHandler([.list]) } else { diff --git a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManager.swift b/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManager.swift index 6b581edf84..ffeba9c84c 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManager.swift +++ b/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManager.swift @@ -7,11 +7,10 @@ // import Foundation -import StoreKit import Logging +import StoreKit class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { - private enum OperationCategory { static let sendAppStoreReceipt = "AppStorePaymentManager.sendAppStoreReceipt" static let productsRequest = "AppStorePaymentManager.productsRequest" @@ -75,7 +74,10 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { // MARK: - SKPaymentTransactionObserver - func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { + func paymentQueue( + _ queue: SKPaymentQueue, + updatedTransactions transactions: [SKPaymentTransaction] + ) { // Ensure that all calls happen on main queue if Thread.isMainThread { handleTransactions(transactions) @@ -98,7 +100,10 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { // MARK: - Products and payments - func requestProducts(with productIdentifiers: Set<AppStoreSubscription>, completionHandler: @escaping (OperationCompletion<SKProductsResponse, Swift.Error>) -> Void) -> Cancellable { + func requestProducts( + with productIdentifiers: Set<AppStoreSubscription>, + completionHandler: @escaping (OperationCompletion<SKProductsResponse, Swift.Error>) -> Void + ) -> Cancellable { let productIdentifiers = productIdentifiers.productIdentifiersSet let operation = ProductsRequestOperation( productIdentifiers: productIdentifiers, @@ -113,9 +118,10 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { func addPayment(_ payment: SKPayment, for accountToken: String) { var task: Cancellable? - let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(withName: "Validate account token") { - task?.cancel() - } + let backgroundTaskIdentifier = UIApplication.shared + .beginBackgroundTask(withName: "Validate account token") { + task?.cancel() + } // Validate account token before adding new payment to the queue. task = accountsProxy.getAccountData( @@ -129,7 +135,7 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { self.associateAccountToken(accountToken, and: payment) self.paymentQueue.add(payment) - case .failure(let error): + case let .failure(error): self.observerList.forEach { observer in observer.appStorePaymentManager( self, @@ -156,11 +162,20 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { } } - func restorePurchases(for accountToken: String, completionHandler: @escaping (OperationCompletion<REST.CreateApplePaymentResponse, AppStorePaymentManager.Error>) -> Void) -> Cancellable { - return sendAppStoreReceipt(accountToken: accountToken, forceRefresh: true, completionHandler: completionHandler) + func restorePurchases( + for accountToken: String, + completionHandler: @escaping (OperationCompletion< + REST.CreateApplePaymentResponse, + AppStorePaymentManager.Error + >) -> Void + ) -> Cancellable { + return sendAppStoreReceipt( + accountToken: accountToken, + forceRefresh: true, + completionHandler: completionHandler + ) } - // MARK: - Private methods private func associateAccountToken(_ token: String, and payment: SKPayment) { @@ -180,7 +195,12 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { } } - private func sendAppStoreReceipt(accountToken: String, forceRefresh: Bool, completionHandler: @escaping (OperationCompletion<REST.CreateApplePaymentResponse, Error>) -> Void) -> Cancellable { + private func sendAppStoreReceipt( + accountToken: String, + forceRefresh: Bool, + completionHandler: @escaping (OperationCompletion<REST.CreateApplePaymentResponse, Error>) + -> Void + ) -> Cancellable { let operation = SendAppStoreReceiptOperation( apiProxy: apiProxy, accountToken: accountToken, @@ -214,7 +234,10 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { logger.info("Deferred \(transaction.payment.productIdentifier)") case .failed: - logger.error("Failed to purchase \(transaction.payment.productIdentifier): \(transaction.error?.localizedDescription ?? "No error")") + logger + .error( + "Failed to purchase \(transaction.payment.productIdentifier): \(transaction.error?.localizedDescription ?? "No error")" + ) didFailPurchase(transaction: transaction) @@ -246,7 +269,8 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { transaction: transaction, payment: transaction.payment, accountToken: accountToken, - didFailWithError: .storePayment(transaction.error!)) + didFailWithError: .storePayment(transaction.error!) + ) } } else { observerList.forEach { observer in @@ -255,7 +279,8 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { transaction: transaction, payment: transaction.payment, accountToken: nil, - didFailWithError: .noAccountSet) + didFailWithError: .noAccountSet + ) } } } @@ -268,14 +293,15 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { transaction: transaction, payment: transaction.payment, accountToken: nil, - didFailWithError: .noAccountSet) + didFailWithError: .noAccountSet + ) } return } _ = sendAppStoreReceipt(accountToken: accountToken, forceRefresh: false) { completion in switch completion { - case .success(let response): + case let .success(response): self.paymentQueue.finishTransaction(transaction) self.observerList.forEach { observer in @@ -283,17 +309,19 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { self, transaction: transaction, accountToken: accountToken, - didFinishWithResponse: response) + didFinishWithResponse: response + ) } - case .failure(let error): + case let .failure(error): self.observerList.forEach { observer in observer.appStorePaymentManager( self, transaction: transaction, payment: transaction.payment, accountToken: accountToken, - didFailWithError: error) + didFailWithError: error + ) } case .cancelled: @@ -301,5 +329,4 @@ class AppStorePaymentManager: NSObject, SKPaymentTransactionObserver { } } } - } diff --git a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerDelegate.swift b/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerDelegate.swift index a56fa2d1e2..8dc848c854 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerDelegate.swift +++ b/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerDelegate.swift @@ -10,9 +10,10 @@ import Foundation import StoreKit protocol AppStorePaymentManagerDelegate: AnyObject { - /// Return the account token associated with the payment. /// Usually called for unfinished transactions coming back after the app was restarted. - func appStorePaymentManager(_ manager: AppStorePaymentManager, - didRequestAccountTokenFor payment: SKPayment) -> String? + func appStorePaymentManager( + _ manager: AppStorePaymentManager, + didRequestAccountTokenFor payment: SKPayment + ) -> String? } diff --git a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentObserver.swift b/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentObserver.swift index ad37c707c4..4a82b8fa87 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentObserver.swift +++ b/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentObserver.swift @@ -10,17 +10,18 @@ import Foundation import StoreKit protocol AppStorePaymentObserver: AnyObject { - func appStorePaymentManager( _ manager: AppStorePaymentManager, transaction: SKPaymentTransaction?, payment: SKPayment, accountToken: String?, - didFailWithError error: AppStorePaymentManager.Error) + didFailWithError error: AppStorePaymentManager.Error + ) func appStorePaymentManager( _ manager: AppStorePaymentManager, transaction: SKPaymentTransaction, accountToken: String, - didFinishWithResponse response: REST.CreateApplePaymentResponse) + didFinishWithResponse response: REST.CreateApplePaymentResponse + ) } diff --git a/ios/MullvadVPN/AppStorePaymentManager/AppStoreSubscription.swift b/ios/MullvadVPN/AppStorePaymentManager/AppStoreSubscription.swift index 4c126bfdc3..37fa3342cb 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/AppStoreSubscription.swift +++ b/ios/MullvadVPN/AppStorePaymentManager/AppStoreSubscription.swift @@ -34,6 +34,6 @@ extension SKProduct { extension Set where Element == AppStoreSubscription { var productIdentifiersSet: Set<String> { - Set<String>(self.map { $0.rawValue }) + Set<String>(map { $0.rawValue }) } } diff --git a/ios/MullvadVPN/AppStorePaymentManager/SendAppStoreReceiptOperation.swift b/ios/MullvadVPN/AppStorePaymentManager/SendAppStoreReceiptOperation.swift index a95707b5c6..04a7813d4a 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/SendAppStoreReceiptOperation.swift +++ b/ios/MullvadVPN/AppStorePaymentManager/SendAppStoreReceiptOperation.swift @@ -9,7 +9,10 @@ import Foundation import Logging -class SendAppStoreReceiptOperation: ResultOperation<REST.CreateApplePaymentResponse, AppStorePaymentManager.Error> { +class SendAppStoreReceiptOperation: ResultOperation< + REST.CreateApplePaymentResponse, + AppStorePaymentManager.Error +> { private let apiProxy: REST.APIProxy private let accountToken: String private let forceRefresh: Bool @@ -19,7 +22,13 @@ class SendAppStoreReceiptOperation: ResultOperation<REST.CreateApplePaymentRespo private let logger = Logger(label: "SendAppStoreReceiptOperation") - init(apiProxy: REST.APIProxy, accountToken: String, forceRefresh: Bool, receiptProperties: [String: Any]?, completionHandler: @escaping CompletionHandler) { + init( + apiProxy: REST.APIProxy, + accountToken: String, + forceRefresh: Bool, + receiptProperties: [String: Any]?, + completionHandler: @escaping CompletionHandler + ) { self.apiProxy = apiProxy self.accountToken = accountToken self.forceRefresh = forceRefresh @@ -41,13 +50,19 @@ class SendAppStoreReceiptOperation: ResultOperation<REST.CreateApplePaymentRespo } override func main() { - fetchReceiptTask = AppStoreReceipt.fetch(forceRefresh: self.forceRefresh, receiptProperties: self.receiptProperties) { completion in + fetchReceiptTask = AppStoreReceipt.fetch( + forceRefresh: forceRefresh, + receiptProperties: receiptProperties + ) { completion in switch completion { - case .success(let receiptData): + case let .success(receiptData): self.sendReceipt(receiptData) - case .failure(let error): - self.logger.error(chainedError: error, message: "Failed to fetch the AppStore receipt.") + case let .failure(error): + self.logger.error( + chainedError: error, + message: "Failed to fetch the AppStore receipt." + ) self.finish(completion: .failure(.readReceipt(error))) case .cancelled: @@ -58,22 +73,29 @@ class SendAppStoreReceiptOperation: ResultOperation<REST.CreateApplePaymentRespo private func sendReceipt(_ receiptData: Data) { submitReceiptTask = apiProxy.createApplePayment( - accountNumber: self.accountToken, + accountNumber: accountToken, receiptString: receiptData, - retryStrategy: .noRetry) { result in - switch result { - case .success(let response): - self.logger.info("AppStore receipt was processed. Time added: \(response.timeAdded), New expiry: \(response.newExpiry.logFormatDate())") - self.finish(completion: .success(response)) + retryStrategy: .noRetry + ) { result in + switch result { + case let .success(response): + self.logger + .info( + "AppStore receipt was processed. Time added: \(response.timeAdded), New expiry: \(response.newExpiry.logFormatDate())" + ) + self.finish(completion: .success(response)) - case .failure(let error): - self.logger.error(chainedError: error, message: "Failed to send the AppStore receipt.") - self.finish(completion: .failure(.sendReceipt(error))) + case let .failure(error): + self.logger.error( + chainedError: error, + message: "Failed to send the AppStore receipt." + ) + self.finish(completion: .failure(.sendReceipt(error))) - case .cancelled: - self.logger.debug("Receipt submission cancelled.") - self.finish(completion: .cancelled) - } + case .cancelled: + self.logger.debug("Receipt submission cancelled.") + self.finish(completion: .cancelled) } + } } } diff --git a/ios/MullvadVPN/AppStoreReceipt.swift b/ios/MullvadVPN/AppStoreReceipt.swift index a565bd4012..c4bb18060c 100644 --- a/ios/MullvadVPN/AppStoreReceipt.swift +++ b/ios/MullvadVPN/AppStoreReceipt.swift @@ -42,7 +42,11 @@ enum AppStoreReceipt { /// Read AppStore receipt from disk or refresh it from AppStore if it's missing. /// This call may trigger a sign in with AppStore prompt to appear. - static func fetch(forceRefresh: Bool = false, receiptProperties: [String: Any]? = nil, completionHandler: @escaping (OperationCompletion<Data, Error>) -> Void) -> Cancellable { + static func fetch( + forceRefresh: Bool = false, + receiptProperties: [String: Any]? = nil, + completionHandler: @escaping (OperationCompletion<Data, Error>) -> Void + ) -> Cancellable { let operation = FetchAppStoreReceiptOperation( forceRefresh: forceRefresh, receiptProperties: receiptProperties, @@ -59,7 +63,9 @@ enum AppStoreReceipt { } } -fileprivate class FetchAppStoreReceiptOperation: ResultOperation<Data, AppStoreReceipt.Error>, SKRequestDelegate { +private class FetchAppStoreReceiptOperation: ResultOperation<Data, AppStoreReceipt.Error>, + SKRequestDelegate +{ private var request: SKReceiptRefreshRequest? private let receiptProperties: [String: Any]? private let forceRefresh: Bool @@ -68,8 +74,7 @@ fileprivate class FetchAppStoreReceiptOperation: ResultOperation<Data, AppStoreR forceRefresh: Bool, receiptProperties: [String: Any]?, completionHandler: @escaping (Completion) -> Void - ) - { + ) { self.forceRefresh = forceRefresh self.receiptProperties = receiptProperties @@ -99,7 +104,7 @@ fileprivate class FetchAppStoreReceiptOperation: ResultOperation<Data, AppStoreR } override func operationDidCancel() { - self.request?.cancel() + request?.cancel() } // - MARK: SKRequestDelegate @@ -150,13 +155,14 @@ fileprivate class FetchAppStoreReceiptOperation: ResultOperation<Data, AppStoreR let readResult = Result { try Data(contentsOf: appStoreReceiptURL) } - return readResult.mapError { (error) -> AppStoreReceipt.Error in - if let cocoaError = error as? CocoaError, cocoaError.code == .fileReadNoSuchFile || cocoaError.code == .fileNoSuchFile { + return readResult.mapError { error -> AppStoreReceipt.Error in + if let cocoaError = error as? CocoaError, + cocoaError.code == .fileReadNoSuchFile || cocoaError.code == .fileNoSuchFile + { return .doesNotExist } else { return .io(error) } } } - } diff --git a/ios/MullvadVPN/ApplicationConfiguration.swift b/ios/MullvadVPN/ApplicationConfiguration.swift index f00413a296..615abf0880 100644 --- a/ios/MullvadVPN/ApplicationConfiguration.swift +++ b/ios/MullvadVPN/ApplicationConfiguration.swift @@ -26,17 +26,24 @@ class ApplicationConfiguration { /// Container URL for security group static var containerURL: URL? { - return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Self.securityGroupIdentifier) + return FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: Self.securityGroupIdentifier) } /// The main application log file located in a shared container static var mainApplicationLogFileURL: URL? { - return Self.containerURL?.appendingPathComponent("Logs/net.mullvad.MullvadVPN.log", isDirectory: false) + return Self.containerURL?.appendingPathComponent( + "Logs/net.mullvad.MullvadVPN.log", + isDirectory: false + ) } /// The packet tunnel log file located in a shared container static var packetTunnelLogFileURL: URL? { - return Self.containerURL?.appendingPathComponent("Logs/net.mullvad.MullvadVPN.PacketTunnel.log", isDirectory: false) + return Self.containerURL?.appendingPathComponent( + "Logs/net.mullvad.MullvadVPN.PacketTunnel.log", + isDirectory: false + ) } /// All log files located in a shared container diff --git a/ios/MullvadVPN/AutomaticKeyboardResponder.swift b/ios/MullvadVPN/AutomaticKeyboardResponder.swift index eef063261d..ee3a97bfa8 100644 --- a/ios/MullvadVPN/AutomaticKeyboardResponder.swift +++ b/ios/MullvadVPN/AutomaticKeyboardResponder.swift @@ -6,8 +6,8 @@ // Copyright © 2021 Mullvad VPN AB. All rights reserved. // -import UIKit import Logging +import UIKit class AutomaticKeyboardResponder { weak var targetView: UIView? @@ -21,13 +21,28 @@ class AutomaticKeyboardResponder { init<T: UIView>(targetView: T, handler: @escaping (T, CGFloat) -> Void) { self.targetView = targetView - self.handler = { (view, adjustment) in + self.handler = { view, adjustment in handler(view as! T, adjustment) } - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: UIWindow.keyboardWillChangeFrameNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIWindow.keyboardWillShowNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidHide(_:)), name: UIWindow.keyboardDidHideNotification, object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillChangeFrame(_:)), + name: UIWindow.keyboardWillChangeFrameNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillShow(_:)), + name: UIWindow.keyboardWillShowNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardDidHide(_:)), + name: UIWindow.keyboardDidHideNotification, + object: nil + ) } func updateContentInsets() { @@ -59,11 +74,12 @@ class AutomaticKeyboardResponder { // MARK: - Private private func handleKeyboardNotification(_ notification: Notification) { - guard let keyboardFrameValue = notification.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? NSValue else { return } + guard let keyboardFrameValue = notification + .userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? NSValue else { return } lastKeyboardRect = keyboardFrameValue.cgRectValue - self.adjustContentInsets(keyboardRect: keyboardFrameValue.cgRectValue) + adjustContentInsets(keyboardRect: keyboardFrameValue.cgRectValue) } private func addPresentationControllerObserver() { @@ -77,11 +93,16 @@ class AutomaticKeyboardResponder { return } - presentationFrameObserver = containerView.observe(\.frame, options: [.new], changeHandler: { [weak self] (containingView, change) in - guard let self = self, let keyboardFrameValue = self.lastKeyboardRect else { return } + presentationFrameObserver = containerView.observe( + \.frame, + options: [.new], + changeHandler: { [weak self] containingView, change in + guard let self = self, + let keyboardFrameValue = self.lastKeyboardRect else { return } - self.adjustContentInsets(keyboardRect: keyboardFrameValue) - }) + self.adjustContentInsets(keyboardRect: keyboardFrameValue) + } + ) } /// Returns the first parent controller in the responder chain @@ -105,7 +126,7 @@ class AutomaticKeyboardResponder { // Find the container view that private `_UIFormSheetPresentationController` moves // along with the keyboard. - return iterator.first { (view) -> Bool in + return iterator.first { view -> Bool in return view.description.starts(with: "<UIDropShadowView") } } @@ -145,10 +166,9 @@ class AutomaticKeyboardResponder { } extension AutomaticKeyboardResponder { - /// A convenience initializer that automatically assigns the offset to the scroll view subclasses convenience init<T: UIScrollView>(targetView: T) { - self.init(targetView: targetView) { (scrollView, offset) in + self.init(targetView: targetView) { scrollView, offset in if scrollView.canBecomeFirstResponder { scrollView.contentInset.bottom = targetView.isFirstResponder ? offset : 0 scrollView.scrollIndicatorInsets.bottom = targetView.isFirstResponder ? offset : 0 diff --git a/ios/MullvadVPN/Bundle+ProductVersion.swift b/ios/MullvadVPN/Bundle+ProductVersion.swift index a2ed526588..1030096834 100644 --- a/ios/MullvadVPN/Bundle+ProductVersion.swift +++ b/ios/MullvadVPN/Bundle+ProductVersion.swift @@ -9,7 +9,6 @@ import Foundation extension Bundle { - /// Returns the product version string based on the following rules: /// /// 1. Dev builds (debug): XXXX.YY-devZ @@ -19,17 +18,17 @@ extension Bundle { /// Note: XXXX.YY is an app version (i.e 2020.5) and Z is a build number (i.e 1) var productVersion: String { let version = object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "???" - let buildNumber = object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String ?? "???" + let buildNumber = object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String ?? + "???" #if DEBUG - return "\(version)-dev\(buildNumber)" + return "\(version)-dev\(buildNumber)" #else - if appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" { - return "\(version)-beta\(buildNumber)" - } else { - return version - } + if appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" { + return "\(version)-beta\(buildNumber)" + } else { + return version + } #endif } - } diff --git a/ios/MullvadVPN/CharacterSet+IPAddress.swift b/ios/MullvadVPN/CharacterSet+IPAddress.swift index f15d134f84..2131398cc9 100644 --- a/ios/MullvadVPN/CharacterSet+IPAddress.swift +++ b/ios/MullvadVPN/CharacterSet+IPAddress.swift @@ -16,5 +16,4 @@ extension CharacterSet { static var ipv6AddressCharset: CharacterSet { return CharacterSet(charactersIn: "0123456789abcdef:.") } - } diff --git a/ios/MullvadVPN/CodingErrors+ChainedError.swift b/ios/MullvadVPN/CodingErrors+ChainedError.swift index e14d61473b..1dfa73f923 100644 --- a/ios/MullvadVPN/CodingErrors+ChainedError.swift +++ b/ios/MullvadVPN/CodingErrors+ChainedError.swift @@ -11,13 +11,13 @@ import Foundation extension DecodingError: CustomChainedErrorDescriptionProtocol { var customErrorDescription: String? { switch self { - case .typeMismatch(let type, let context): + case let .typeMismatch(type, context): return "Type mismatch, expected \(type) for key at \"\(context.codingPath.codingPathString)\"." - case .valueNotFound(_, let context): + case let .valueNotFound(_, context): return "Value not found at \"\(context.codingPath.codingPathString)\"." - case .keyNotFound(let codingKey, let context): + case let .keyNotFound(codingKey, context): return "Key \"\(codingKey.stringValue)\" not found at \"\(context.codingPath.codingPathString)\"." case .dataCorrupted: @@ -32,7 +32,7 @@ extension DecodingError: CustomChainedErrorDescriptionProtocol { extension EncodingError: CustomChainedErrorDescriptionProtocol { var customErrorDescription: String? { switch self { - case .invalidValue(_, let context): + case let .invalidValue(_, context): return "Invalid value at \"\(context.codingPath.codingPathString)\"" @unknown default: diff --git a/ios/MullvadVPN/ConnectContentView.swift b/ios/MullvadVPN/ConnectContentView.swift index 01b22f18d9..f158aa90eb 100644 --- a/ios/MullvadVPN/ConnectContentView.swift +++ b/ios/MullvadVPN/ConnectContentView.swift @@ -6,8 +6,8 @@ // Copyright © 2021 Mullvad VPN AB. All rights reserved. // -import UIKit import MapKit +import UIKit class ConnectContentView: UIView { enum ActionButton { @@ -84,13 +84,9 @@ class ConnectContentView: UIView { return button }() - lazy var selectLocationBlurView: TranslucentButtonBlurView = { - return TranslucentButtonBlurView(button: selectLocationButton) - }() + lazy var selectLocationBlurView = TranslucentButtonBlurView(button: selectLocationButton) - lazy var cancelButtonBlurView: TranslucentButtonBlurView = { - return TranslucentButtonBlurView(button: cancelButton) - }() + lazy var cancelButtonBlurView = TranslucentButtonBlurView(button: cancelButton) let splitDisconnectButton: DisconnectSplitButton = { let button = DisconnectSplitButton() @@ -139,7 +135,7 @@ class ConnectContentView: UIView { } private func addSubviews() { - mapView.frame = self.bounds + mapView.frame = bounds locationContainerView.addSubview(secureLabel) locationContainerView.addSubview(cityLabel) @@ -158,12 +154,16 @@ class ConnectContentView: UIView { containerView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), containerView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), - locationContainerView.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor), + locationContainerView.topAnchor + .constraint(greaterThanOrEqualTo: containerView.topAnchor), locationContainerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), locationContainerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), activityIndicator.centerXAnchor.constraint(equalTo: mapView.centerXAnchor), - locationContainerView.topAnchor.constraint(equalTo: activityIndicator.bottomAnchor, constant: 22), + locationContainerView.topAnchor.constraint( + equalTo: activityIndicator.bottomAnchor, + constant: 22 + ), secureLabel.topAnchor.constraint(equalTo: locationContainerView.topAnchor), secureLabel.leadingAnchor.constraint(equalTo: locationContainerView.leadingAnchor), @@ -178,14 +178,20 @@ class ConnectContentView: UIView { countryLabel.trailingAnchor.constraint(equalTo: locationContainerView.trailingAnchor), countryLabel.bottomAnchor.constraint(equalTo: locationContainerView.bottomAnchor), - connectionPanel.topAnchor.constraint(equalTo: locationContainerView.bottomAnchor, constant: 8), + connectionPanel.topAnchor.constraint( + equalTo: locationContainerView.bottomAnchor, + constant: 8 + ), connectionPanel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), connectionPanel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), - buttonsStackView.topAnchor.constraint(equalTo: connectionPanel.bottomAnchor, constant: 24), + buttonsStackView.topAnchor.constraint( + equalTo: connectionPanel.bottomAnchor, + constant: 24 + ), buttonsStackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), buttonsStackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), - buttonsStackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) + buttonsStackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), ]) updateTraitConstraints() @@ -197,16 +203,24 @@ class ConnectContentView: UIView { switch traitCollection.userInterfaceIdiom { case .pad: // Max container width is 70% width of iPad in portrait mode - let maxWidth = min(UIScreen.main.nativeBounds.width * 0.7, UIMetrics.maximumSplitViewContentContainerWidth) + let maxWidth = min( + UIScreen.main.nativeBounds.width * 0.7, + UIMetrics.maximumSplitViewContentContainerWidth + ) layoutConstraints.append(contentsOf: [ - containerView.trailingAnchor.constraint(lessThanOrEqualTo: layoutMarginsGuide.trailingAnchor), + containerView.trailingAnchor + .constraint(lessThanOrEqualTo: layoutMarginsGuide.trailingAnchor), containerView.widthAnchor.constraint(equalToConstant: maxWidth) - .withPriority(.defaultHigh) + .withPriority(.defaultHigh), ]) case .phone: - layoutConstraints.append(containerView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor)) + layoutConstraints + .append( + containerView.trailingAnchor + .constraint(equalTo: layoutMarginsGuide.trailingAnchor) + ) default: break @@ -226,14 +240,14 @@ class ConnectContentView: UIView { } private func setArrangedButtons(_ newButtons: [UIView]) { - buttonsStackView.arrangedSubviews.forEach { (button) in + buttonsStackView.arrangedSubviews.forEach { button in if !newButtons.contains(button) { buttonsStackView.removeArrangedSubview(button) button.removeFromSuperview() } } - newButtons.forEach { (button) in + newButtons.forEach { button in buttonsStackView.addArrangedSubview(button) } } diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift index 6a3fe21d0c..02e4a5ee6e 100644 --- a/ios/MullvadVPN/ConnectViewController.swift +++ b/ios/MullvadVPN/ConnectViewController.swift @@ -6,13 +6,13 @@ // Copyright © 2019 Mullvad VPN AB. All rights reserved. // -import UIKit -import MapKit import Logging +import MapKit +import UIKit class CustomOverlayRenderer: MKOverlayRenderer { override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) { - let drawRect = self.rect(for: mapRect) + let drawRect = rect(for: mapRect) context.setFillColor(UIColor.secondaryColor.cgColor) context.fill(drawRect) } @@ -23,7 +23,6 @@ protocol ConnectViewControllerDelegate: AnyObject { } class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainment, TunnelObserver { - private static let geoJSONSourceFileName = "countries.geo.json" private static let locationMarkerReuseIdentifier = "location" @@ -82,15 +81,35 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen override func viewDidLoad() { super.viewDidLoad() - contentView.connectButton.addTarget(self, action: #selector(handleConnect(_:)), for: .touchUpInside) - contentView.cancelButton.addTarget(self, action: #selector(handleDisconnect(_:)), for: .touchUpInside) - contentView.splitDisconnectButton.primaryButton.addTarget(self, action: #selector(handleDisconnect(_:)), for: .touchUpInside) - contentView.splitDisconnectButton.secondaryButton.addTarget(self, action: #selector(handleReconnect(_:)), for: .touchUpInside) + contentView.connectButton.addTarget( + self, + action: #selector(handleConnect(_:)), + for: .touchUpInside + ) + contentView.cancelButton.addTarget( + self, + action: #selector(handleDisconnect(_:)), + for: .touchUpInside + ) + contentView.splitDisconnectButton.primaryButton.addTarget( + self, + action: #selector(handleDisconnect(_:)), + for: .touchUpInside + ) + contentView.splitDisconnectButton.secondaryButton.addTarget( + self, + action: #selector(handleReconnect(_:)), + for: .touchUpInside + ) - contentView.selectLocationButton.addTarget(self, action: #selector(handleSelectLocation(_:)), for: .touchUpInside) + contentView.selectLocationButton.addTarget( + self, + action: #selector(handleSelectLocation(_:)), + for: .touchUpInside + ) TunnelManager.shared.addObserver(self) - self.tunnelState = TunnelManager.shared.tunnelStatus.state + tunnelState = TunnelManager.shared.tunnelStatus.state addSubviews() setupMapView() @@ -104,7 +123,8 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen super.traitCollectionDidChange(previousTraitCollection) if previousTraitCollection?.userInterfaceIdiom != traitCollection.userInterfaceIdiom || - previousTraitCollection?.horizontalSizeClass != traitCollection.horizontalSizeClass { + previousTraitCollection?.horizontalSizeClass != traitCollection.horizontalSizeClass + { updateTraitDependentViews() } } @@ -128,14 +148,17 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen contentView.topAnchor.constraint(equalTo: view.topAnchor), contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) // Force layout since we rely on view frames when positioning map camera. view.layoutIfNeeded() } - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + override func viewWillTransition( + to size: CGSize, + with coordinator: UIViewControllerTransitionCoordinator + ) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { _ in }, completion: { context in @@ -149,7 +172,10 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen // no-op } - func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2) { + func tunnelManager( + _ manager: TunnelManager, + didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2 + ) { // no-op } @@ -179,14 +205,18 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen comment: "" ), for: .normal ) - contentView.selectLocationButton.setTitle(tunnelState.localizedTitleForSelectLocationButton, for: .normal) + contentView.selectLocationButton.setTitle( + tunnelState.localizedTitleForSelectLocationButton, + for: .normal + ) contentView.cancelButton.setTitle( NSLocalizedString( "CANCEL_BUTTON_TITLE", tableName: "Main", value: "Cancel", comment: "" - ), for: .normal) + ), for: .normal + ) contentView.splitDisconnectButton.primaryButton.setTitle( NSLocalizedString( "DISCONNECT_BUTTON_TITLE", @@ -206,7 +236,7 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen } private func updateTraitDependentViews() { - contentView.setActionButtons(tunnelState.actionButtons(traitCollection: self.traitCollection)) + contentView.setActionButtons(tunnelState.actionButtons(traitCollection: traitCollection)) } private func attributedStringForLocation(string: String) -> NSAttributedString { @@ -214,28 +244,32 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen paragraphStyle.lineSpacing = 0 paragraphStyle.lineHeightMultiple = 0.80 return NSAttributedString(string: string, attributes: [ - .paragraphStyle: paragraphStyle]) + .paragraphStyle: paragraphStyle, + ]) } private func updateTunnelRelay() { switch tunnelState { - case .connecting(let tunnelRelay): + case let .connecting(tunnelRelay): setTunnelRelay(tunnelRelay) - case .connected(let tunnelRelay), .reconnecting(let tunnelRelay): + case let .connected(tunnelRelay), let .reconnecting(tunnelRelay): setTunnelRelay(tunnelRelay) case .disconnected, .disconnecting, .pendingReconnect: setTunnelRelay(nil) } - contentView.locationContainerView.accessibilityLabel = tunnelState.localizedAccessibilityLabel + contentView.locationContainerView.accessibilityLabel = tunnelState + .localizedAccessibilityLabel } private func setTunnelRelay(_ tunnelRelay: PacketTunnelRelay?) { if let tunnelRelay = tunnelRelay { - contentView.cityLabel.attributedText = attributedStringForLocation(string: tunnelRelay.location.city) - contentView.countryLabel.attributedText = attributedStringForLocation(string: tunnelRelay.location.country) + contentView.cityLabel + .attributedText = attributedStringForLocation(string: tunnelRelay.location.city) + contentView.countryLabel + .attributedText = attributedStringForLocation(string: tunnelRelay.location.country) contentView.connectionPanel.dataSource = ConnectionPanelData( inAddress: "\(tunnelRelay.ipv4Relay) UDP", @@ -253,7 +287,10 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen private func locationMarkerOffset() -> CGPoint { // Compute the activity indicator frame within the view coordinate system. - let activityIndicatorFrame = contentView.activityIndicator.convert(contentView.activityIndicator.bounds, to: view) + let activityIndicatorFrame = contentView.activityIndicator.convert( + contentView.activityIndicator.bounds, + to: view + ) // Compute the offset to align the marker on the map with activity indicator. let offsetY = activityIndicatorFrame.midY - contentView.mapView.frame.midY @@ -261,9 +298,13 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen return CGPoint(x: 0, y: offsetY) } - private func computeCoordinateRegion(center: CLLocationCoordinate2D, offset: CGPoint) -> MKCoordinateRegion { + private func computeCoordinateRegion( + center: CLLocationCoordinate2D, + offset: CGPoint + ) -> MKCoordinateRegion { let span = MKCoordinateSpan(latitudeDelta: 30, longitudeDelta: 30) - var region = contentView.mapView.regionThatFits(MKCoordinateRegion(center: center, span: span)) + var region = contentView.mapView + .regionThatFits(MKCoordinateRegion(center: center, span: span)) let latitudeDeltaPerPoint = region.span.latitudeDelta / contentView.mapView.frame.height region.center = center @@ -274,7 +315,7 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen private func updateLocation(animated: Bool) { switch tunnelState { - case .connecting(let tunnelRelay): + case let .connecting(tunnelRelay): removeLocationMarker() contentView.activityIndicator.startAnimating() @@ -284,13 +325,13 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen unsetLocation(animated: animated) } - case .reconnecting(let tunnelRelay): + case let .reconnecting(tunnelRelay): removeLocationMarker() contentView.activityIndicator.startAnimating() setLocation(coordinate: tunnelRelay.location.geoCoordinate, animated: animated) - case .connected(let tunnelRelay): + case let .connected(tunnelRelay): // Show marker right away if activity indicator is not animating, i.e when the app // launches with connected tunnel. let showMarkerRightAway = !contentView.activityIndicator.isAnimating @@ -299,7 +340,10 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen addLocationMarker(coordinate: tunnelRelay.location.geoCoordinate) } - setLocation(coordinate: tunnelRelay.location.geoCoordinate, animated: animated) { [weak self] in + setLocation( + coordinate: tunnelRelay.location.geoCoordinate, + animated: animated + ) { [weak self] in if !showMarkerRightAway { self?.contentView.activityIndicator.stopAnimating() self?.addLocationMarker(coordinate: tunnelRelay.location.geoCoordinate) @@ -327,7 +371,11 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen contentView.mapView.removeAnnotation(locationMarker) } - private func setLocation(coordinate: CLLocationCoordinate2D, animated: Bool, animationDidEnd: (() -> Void)? = nil) { + private func setLocation( + coordinate: CLLocationCoordinate2D, + animated: Bool, + animationDidEnd: (() -> Void)? = nil + ) { let markerOffset = locationMarkerOffset() let region = computeCoordinateRegion(center: coordinate, offset: markerOffset) @@ -378,7 +426,7 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen notificationView.topAnchor.constraint(equalTo: view.topAnchor), notificationView.leadingAnchor.constraint(equalTo: view.leadingAnchor), notificationView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - notificationView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + notificationView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) } @@ -491,7 +539,6 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen } private extension TunnelState { - var textColorForSecureLabel: UIColor { switch self { case .connecting, .reconnecting: @@ -585,7 +632,7 @@ private extension TunnelState { comment: "" ) - case .connected(let tunnelInfo): + case let .connected(tunnelInfo): return String( format: NSLocalizedString( "TUNNEL_STATE_CONNECTED_ACCESSIBILITY_LABEL", @@ -605,7 +652,7 @@ private extension TunnelState { comment: "" ) - case .reconnecting(let tunnelInfo): + case let .reconnecting(tunnelInfo): return String( format: NSLocalizedString( "TUNNEL_STATE_RECONNECTING_ACCESSIBILITY_LABEL", @@ -665,7 +712,6 @@ private extension TunnelState { return [] } } - } private extension MKCoordinateRegion { @@ -675,5 +721,4 @@ private extension MKCoordinateRegion { fabs(span.latitudeDelta - other.span.latitudeDelta) <= .ulpOfOne && fabs(span.longitudeDelta - other.span.longitudeDelta) <= .ulpOfOne } - } diff --git a/ios/MullvadVPN/ConnectionPanelView.swift b/ios/MullvadVPN/ConnectionPanelView.swift index c4ea1c7b6a..1a491d1667 100644 --- a/ios/MullvadVPN/ConnectionPanelView.swift +++ b/ios/MullvadVPN/ConnectionPanelView.swift @@ -15,7 +15,6 @@ struct ConnectionPanelData { } class ConnectionPanelView: UIView { - var dataSource: ConnectionPanelData? { didSet { didChangeDataSource() @@ -28,7 +27,7 @@ class ConnectionPanelView: UIView { } } - var connectedRelayName: String = "" { + var connectedRelayName = "" { didSet { collapseButton.setTitle(connectedRelayName, for: .normal) collapseButton.accessibilityLabel = NSLocalizedString( @@ -38,8 +37,11 @@ class ConnectionPanelView: UIView { comment: "" ) collapseButton.accessibilityAttributedValue = NSAttributedString( - string: connectedRelayName.replacingOccurrences(of: "-wireguard", with: " WireGuard"), - attributes: [ .accessibilitySpeechLanguage: "en" ] + string: connectedRelayName.replacingOccurrences( + of: "-wireguard", + with: " WireGuard" + ), + attributes: [.accessibilitySpeechLanguage: "en"] ) } } @@ -107,7 +109,7 @@ class ConnectionPanelView: UIView { textLabelLayoutGuide.trailingAnchor .constraint(equalTo: inAddressRow.textLabelLayoutGuide.trailingAnchor), textLabelLayoutGuide.trailingAnchor - .constraint(equalTo: outAddressRow.textLabelLayoutGuide.trailingAnchor) + .constraint(equalTo: outAddressRow.textLabelLayoutGuide.trailingAnchor), ]) updateConnectionInfoVisibility() @@ -138,7 +140,10 @@ class ConnectionPanelView: UIView { collapseButton.style = showsConnectionInfo ? .up : .down if collapseButton.accessibilityElementIsFocused(), showsConnectionInfo { - UIAccessibility.post(notification: .layoutChanged, argument: stackView.arrangedSubviews.first) + UIAccessibility.post( + notification: .layoutChanged, + argument: stackView.arrangedSubviews.first + ) } updateCollapseButtonAccessibilityHint() } @@ -163,7 +168,6 @@ class ConnectionPanelView: UIView { } class ConnectionPanelAddressRow: UIView { - private let textLabel: UILabel = { let textLabel = UILabel() textLabel.font = .systemFont(ofSize: 17) @@ -227,7 +231,7 @@ class ConnectionPanelAddressRow: UIView { textLabelLayoutGuide.leadingAnchor.constraint(equalTo: textLabel.leadingAnchor), textLabelLayoutGuide.trailingAnchor.constraint(equalTo: textLabel.trailingAnchor), textLabelLayoutGuide.topAnchor.constraint(equalTo: textLabel.topAnchor), - textLabelLayoutGuide.bottomAnchor.constraint(equalTo: textLabel.bottomAnchor) + textLabelLayoutGuide.bottomAnchor.constraint(equalTo: textLabel.bottomAnchor), ]) } @@ -237,7 +241,6 @@ class ConnectionPanelAddressRow: UIView { } class ConnectionPanelCollapseButton: CustomButton { - enum Style { case up, down @@ -282,5 +285,4 @@ class ConnectionPanelCollapseButton: CustomButton { private func updateButtonImage() { setImage(style.image, for: .normal) } - } diff --git a/ios/MullvadVPN/ConsolidatedApplicationLog.swift b/ios/MullvadVPN/ConsolidatedApplicationLog.swift index bd11fac1c9..b7e00b2ea9 100644 --- a/ios/MullvadVPN/ConsolidatedApplicationLog.swift +++ b/ios/MullvadVPN/ConsolidatedApplicationLog.swift @@ -33,9 +33,9 @@ class ConsolidatedApplicationLog: TextOutputStreamable { var errorDescription: String? { switch self { - case .logFileDoesNotExist(let path): + case let .logFileDoesNotExist(path): return "Log file does not exist: \(path)." - case .invalidLogFileURL(let url): + case let .invalidLogFileURL(url): return "Invalid log file URL: \(url.absoluteString)." } } @@ -47,13 +47,18 @@ class ConsolidatedApplicationLog: TextOutputStreamable { private var logs: [LogAttachment] = [] - init(redactCustomStrings: [String], redactContainerPathsForSecurityGroupIdentifiers securityGroupIdentifiers: [String]) { - self.metadata = Self.makeMetadata() + init( + redactCustomStrings: [String], + redactContainerPathsForSecurityGroupIdentifiers securityGroupIdentifiers: [String] + ) { + metadata = Self.makeMetadata() self.redactCustomStrings = redactCustomStrings - applicationGroupContainers = securityGroupIdentifiers.compactMap { (securityGroupIdentifier) -> URL? in - return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: securityGroupIdentifier) - } + applicationGroupContainers = securityGroupIdentifiers + .compactMap { securityGroupIdentifier -> URL? in + return FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: securityGroupIdentifier) + } } func addLogFile(fileURL: URL, includeLogBackup: Bool) { @@ -119,12 +124,13 @@ class ConsolidatedApplicationLog: TextOutputStreamable { private static func makeMetadata() -> Metadata { let osVersion = ProcessInfo.processInfo.operatingSystemVersion - let osVersionString = "iOS \(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)" + let osVersionString = + "iOS \(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)" return [ - .id : UUID().uuidString, + .id: UUID().uuidString, .productVersion: Bundle.main.productVersion, - .os: osVersionString + .os: osVersionString, ] } @@ -142,59 +148,74 @@ class ConsolidatedApplicationLog: TextOutputStreamable { let data = fileHandle.readData(ofLength: Int(kLogMaxReadBytes)) let replacementCharacter = Character(UTF8.decode(UTF8.encodedReplacementCharacter)) - let lossyString = String(String(decoding: data, as: UTF8.self) - .drop { ch in - // Drop leading replacement characters produced when decoding data - return ch == replacementCharacter - }) + let lossyString = String( + String(decoding: data, as: UTF8.self) + .drop { ch in + // Drop leading replacement characters produced when decoding data + return ch == replacementCharacter + } + ) return lossyString } private func redactCustomStrings(string: String) -> String { - return redactCustomStrings.reduce(string) { (resultString, redact) -> String in + return redactCustomStrings.reduce(string) { resultString, redact -> String in return resultString.replacingOccurrences(of: redact, with: kRedactedPlaceholder) } } private func redact(string: String) -> String { return [ - self.redactContainerPaths, + redactContainerPaths, Self.redactAccountNumber, Self.redactIPv4Address, Self.redactIPv6Address, - self.redactCustomStrings - ].reduce(string) { (resultString, transform) -> String in + redactCustomStrings, + ].reduce(string) { resultString, transform -> String in return transform(resultString) } } private func redactContainerPaths(string: String) -> String { - return applicationGroupContainers.reduce(string) { (resultString, containerURL) -> String in - return resultString.replacingOccurrences(of: containerURL.path, with: kRedactedContainerPlaceholder) + return applicationGroupContainers.reduce(string) { resultString, containerURL -> String in + return resultString.replacingOccurrences( + of: containerURL.path, + with: kRedactedContainerPlaceholder + ) } } private static func redactAccountNumber(string: String) -> String { - return redact(regularExpression: try! NSRegularExpression(pattern: #"\d{16}"#), - string: string, - replacementString: kRedactedAccountPlaceholder) + return redact( + regularExpression: try! NSRegularExpression(pattern: #"\d{16}"#), + string: string, + replacementString: kRedactedAccountPlaceholder + ) } private static func redactIPv4Address(string: String) -> String { - return redact(regularExpression: NSRegularExpression.ipv4RegularExpression, - string: string, - replacementString: kRedactedPlaceholder) + return redact( + regularExpression: NSRegularExpression.ipv4RegularExpression, + string: string, + replacementString: kRedactedPlaceholder + ) } private static func redactIPv6Address(string: String) -> String { - return redact(regularExpression: NSRegularExpression.ipv6RegularExpression, - string: string, - replacementString: kRedactedPlaceholder) + return redact( + regularExpression: NSRegularExpression.ipv6RegularExpression, + string: string, + replacementString: kRedactedPlaceholder + ) } - private static func redact(regularExpression: NSRegularExpression, string: String, replacementString: String) -> String { - let nsRange = NSRange((string.startIndex..<string.endIndex), in: string) + private static func redact( + regularExpression: NSRegularExpression, + string: String, + replacementString: String + ) -> String { + let nsRange = NSRange(string.startIndex ..< string.endIndex, in: string) let template = NSRegularExpression.escapedTemplate(for: replacementString) return regularExpression.stringByReplacingMatches( @@ -204,5 +225,4 @@ class ConsolidatedApplicationLog: TextOutputStreamable { withTemplate: template ) } - } diff --git a/ios/MullvadVPN/CustomDateComponentsFormatting.swift b/ios/MullvadVPN/CustomDateComponentsFormatting.swift index d9c12dabff..07c2867ab2 100644 --- a/ios/MullvadVPN/CustomDateComponentsFormatting.swift +++ b/ios/MullvadVPN/CustomDateComponentsFormatting.swift @@ -11,7 +11,6 @@ import Foundation enum CustomDateComponentsFormatting {} extension CustomDateComponentsFormatting { - /// Format a duration between the given dates returning a string that only contains one unit. /// /// The behaviour of that method differs from `DateComponentsFormatter`: @@ -25,8 +24,8 @@ extension CustomDateComponentsFormatting { from start: Date, to end: Date, calendar: Calendar = Calendar.current, - unitsStyle: DateComponentsFormatter.UnitsStyle) -> String? - { + unitsStyle: DateComponentsFormatter.UnitsStyle + ) -> String? { let formatter = DateComponentsFormatter() formatter.calendar = calendar formatter.unitsStyle = unitsStyle @@ -41,16 +40,16 @@ extension CustomDateComponentsFormatting { let minutes = dateComponents.minute ?? 0 let seconds = dateComponents.second ?? 0 - if days == 0 && hours == 0 && minutes == 0 && seconds < 60 { + if days == 0, hours == 0, minutes == 0, seconds < 60 { return NSLocalizedString( "LESS_THAN_ONE_MINUTE", tableName: "CustomDateComponentsFormatting", value: "Less than a minute", comment: "Phrase used for less than 1 minute duration." ) - } else if days == 0 && hours == 23 && minutes >= 30 { + } else if days == 0, hours == 23, minutes >= 30 { return formatter.string(from: DateComponents(calendar: calendar, day: 1)) - } else if days >= 1 && days <= 90 { + } else if days >= 1, days <= 90 { formatter.allowedUnits = [.day] return formatter.string(from: dateComponents) } else { diff --git a/ios/MullvadVPN/CustomNavigationBar.swift b/ios/MullvadVPN/CustomNavigationBar.swift index 7f27dbe7c6..5d56f92e5b 100644 --- a/ios/MullvadVPN/CustomNavigationBar.swift +++ b/ios/MullvadVPN/CustomNavigationBar.swift @@ -10,36 +10,46 @@ import UIKit class CustomNavigationBar: UINavigationBar { private static let titleTextAttributes: [NSAttributedString.Key: Any] = [ - .foregroundColor: UIColor.NavigationBar.titleColor + .foregroundColor: UIColor.NavigationBar.titleColor, ] private static let backButtonTitlePositionOffset = UIOffset(horizontal: 4, vertical: 0) private static let backButtonTitleTextAttributes: [NSAttributedString.Key: Any] = [ - .foregroundColor: UIColor.NavigationBar.backButtonTitleColor + .foregroundColor: UIColor.NavigationBar.backButtonTitleColor, ] private static let setupAppearanceForIOS12Once: Void = { if #available(iOS 13, *) { // no-op } else { - let buttonAppearance = UIBarButtonItem.appearance(whenContainedInInstancesOf: [CustomNavigationBar.self]) - buttonAppearance.setBackButtonTitlePositionAdjustment(CustomNavigationBar.backButtonTitlePositionOffset, for: .default) - buttonAppearance.setTitleTextAttributes(CustomNavigationBar.titleTextAttributes, for: .normal) + let buttonAppearance = UIBarButtonItem + .appearance(whenContainedInInstancesOf: [CustomNavigationBar.self]) + buttonAppearance.setBackButtonTitlePositionAdjustment( + CustomNavigationBar.backButtonTitlePositionOffset, + for: .default + ) + buttonAppearance.setTitleTextAttributes( + CustomNavigationBar.titleTextAttributes, + for: .normal + ) } }() private let customBackIndicatorImage = UIImage(named: "IconBack")? - .backport_withTintColor(UIColor.NavigationBar.backButtonIndicatorColor, renderingMode: .alwaysOriginal) + .backport_withTintColor( + UIColor.NavigationBar.backButtonIndicatorColor, + renderingMode: .alwaysOriginal + ) private let customBackIndicatorTransitionMask = UIImage(named: "IconBackTransitionMask") // Returns the distance from the title label to the bottom of navigation bar var titleLabelBottomInset: CGFloat { // Go two levels deep only - let subviewsToExamine = subviews.flatMap { (view) -> [UIView] in + let subviewsToExamine = subviews.flatMap { view -> [UIView] in return [view] + view.subviews } - let titleLabel = subviewsToExamine.first { (view) -> Bool in + let titleLabel = subviewsToExamine.first { view -> Bool in return view is UILabel } @@ -109,14 +119,19 @@ class CustomNavigationBar: UINavigationBar { navigationBarAppearance.backButtonAppearance = backButtonAppearance if #available(iOS 14, *) { - navigationBarAppearance.setBackIndicatorImage(customBackIndicatorImage, transitionMaskImage: customBackIndicatorTransitionMask) + navigationBarAppearance.setBackIndicatorImage( + customBackIndicatorImage, + transitionMaskImage: customBackIndicatorTransitionMask + ) } else { // Bug: on iOS 13 setBackIndicatorImage accepts parameters in backward order // https://stackoverflow.com/a/58171229/351305 - navigationBarAppearance.setBackIndicatorImage(customBackIndicatorTransitionMask, transitionMaskImage: customBackIndicatorImage) + navigationBarAppearance.setBackIndicatorImage( + customBackIndicatorTransitionMask, + transitionMaskImage: customBackIndicatorImage + ) } return navigationBarAppearance } - } diff --git a/ios/MullvadVPN/CustomNavigationController.swift b/ios/MullvadVPN/CustomNavigationController.swift index 2f7d29f289..a19136b0c8 100644 --- a/ios/MullvadVPN/CustomNavigationController.swift +++ b/ios/MullvadVPN/CustomNavigationController.swift @@ -15,11 +15,11 @@ enum NavigationPopTrigger { } protocol ConditionalNavigation: AnyObject { - func shouldPopNavigationItem(_ navigationItem: UINavigationItem, trigger: NavigationPopTrigger) -> Bool + func shouldPopNavigationItem(_ navigationItem: UINavigationItem, trigger: NavigationPopTrigger) + -> Bool } class CustomNavigationController: UINavigationController, UINavigationBarDelegate { - private static let classInit: Void = { swizzleMethod( aClass: CustomNavigationController.self, @@ -35,13 +35,19 @@ class CustomNavigationController: UINavigationController, UINavigationBarDelegat _ = Self.classInit - popGestureRecognizerDelegate = CustomPopGestureRecognizerDelegate(navigationController: self, systemGestureRecognizerDelegate: interactivePopGestureRecognizer?.delegate) + popGestureRecognizerDelegate = CustomPopGestureRecognizerDelegate( + navigationController: self, + systemGestureRecognizerDelegate: interactivePopGestureRecognizer?.delegate + ) // Replace the system interactive gesture recognizer interactivePopGestureRecognizer?.delegate = popGestureRecognizerDelegate } - @objc dynamic func customNavigationController_navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { + @objc dynamic func customNavigationController_navigationBar( + _ navigationBar: UINavigationBar, + shouldPop item: UINavigationItem + ) -> Bool { var shouldPop = true if let conformingViewController = topViewController as? ConditionalNavigation { @@ -59,11 +65,13 @@ class CustomNavigationController: UINavigationController, UINavigationBarDelegat } private class CustomPopGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { - private let systemGestureRecognizerDelegate: UIGestureRecognizerDelegate? private weak var navigationController: UINavigationController? - init(navigationController: UINavigationController, systemGestureRecognizerDelegate: UIGestureRecognizerDelegate?) { + init( + navigationController: UINavigationController, + systemGestureRecognizerDelegate: UIGestureRecognizerDelegate? + ) { self.navigationController = navigationController self.systemGestureRecognizerDelegate = systemGestureRecognizerDelegate } @@ -89,14 +97,20 @@ private class CustomPopGestureRecognizerDelegate: NSObject, UIGestureRecognizerD // MARK: - UIGestureRecognizerDelegate func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - let shouldBegin = systemGestureRecognizerDelegate?.gestureRecognizerShouldBegin?(gestureRecognizer) ?? true + let shouldBegin = systemGestureRecognizerDelegate? + .gestureRecognizerShouldBegin?(gestureRecognizer) ?? true guard let navigationController = navigationController, - let topItem = navigationController.navigationBar.topItem, - let conformingViewController = navigationController.topViewController as? ConditionalNavigation else { - return shouldBegin + let topItem = navigationController.navigationBar.topItem, + let conformingViewController = navigationController + .topViewController as? ConditionalNavigation + else { + return shouldBegin } - return shouldBegin && conformingViewController.shouldPopNavigationItem(topItem, trigger: .interactiveGesture) + return shouldBegin && conformingViewController.shouldPopNavigationItem( + topItem, + trigger: .interactiveGesture + ) } } diff --git a/ios/MullvadVPN/CustomSplitViewController.swift b/ios/MullvadVPN/CustomSplitViewController.swift index 662eef869b..752eae49e9 100644 --- a/ios/MullvadVPN/CustomSplitViewController.swift +++ b/ios/MullvadVPN/CustomSplitViewController.swift @@ -9,7 +9,6 @@ import UIKit class CustomSplitViewController: UISplitViewController, RootContainment { - var preferredHeaderBarPresentation: HeaderBarPresentation { for case let viewController as RootContainment in viewControllers { return viewController.preferredHeaderBarPresentation @@ -60,11 +59,11 @@ class CustomSplitViewController: UISplitViewController, RootContainment { } private var dividerView: UIView? { - let subviews = view.subviews.flatMap { (view) -> [UIView] in + let subviews = view.subviews.flatMap { view -> [UIView] in return [view] + view.subviews } - return subviews.first { (view) -> Bool in + return subviews.first { view -> Bool in return view.description.hasPrefix("<UIPanelBorderView") } } @@ -75,18 +74,23 @@ class CustomSplitViewController: UISplitViewController, RootContainment { dividerView?.backgroundColor = dividerColor } - override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? { - guard let traitCollection = super.overrideTraitCollection(forChild: childViewController) else { return nil } + override func overrideTraitCollection(forChild childViewController: UIViewController) + -> UITraitCollection? + { + guard let traitCollection = super.overrideTraitCollection(forChild: childViewController) + else { return nil } // Pass the split controller's horizontal size class to the primary controller when split // view is expanded. - if !self.isCollapsed, childViewController == self.viewControllers.last { - let sizeOverrideTraitCollection = UITraitCollection(horizontalSizeClass: self.traitCollection.horizontalSizeClass) + if !isCollapsed, childViewController == viewControllers.last { + let sizeOverrideTraitCollection = UITraitCollection( + horizontalSizeClass: self + .traitCollection.horizontalSizeClass + ) return UITraitCollection(traitsFrom: [traitCollection, sizeOverrideTraitCollection]) } else { return traitCollection } } - } diff --git a/ios/MullvadVPN/CustomSwitch.swift b/ios/MullvadVPN/CustomSwitch.swift index d0eb29aeca..fb3d5bc3eb 100644 --- a/ios/MullvadVPN/CustomSwitch.swift +++ b/ios/MullvadVPN/CustomSwitch.swift @@ -9,16 +9,15 @@ import UIKit class CustomSwitch: UISwitch { - /// Returns the private `UISwitch` background view private var backgroundView: UIView? { // Go two levels deep only - let subviewsToExamine = subviews.flatMap { (view) -> [UIView] in + let subviewsToExamine = subviews.flatMap { view -> [UIView] in return [view] + view.subviews } // Find the first subview that has background color set. - let backgroundView = subviewsToExamine.first { (subview) in + let backgroundView = subviewsToExamine.first { subview in return subview.backgroundColor != nil } @@ -35,7 +34,7 @@ class CustomSwitch: UISwitch { overrideUserInterfaceStyle = .light } - updateThumbColor(isOn: self.isOn, animated: false) + updateThumbColor(isOn: isOn, animated: false) addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged) } diff --git a/ios/MullvadVPN/CustomSwitchContainer.swift b/ios/MullvadVPN/CustomSwitchContainer.swift index 7acafc4495..55316f2929 100644 --- a/ios/MullvadVPN/CustomSwitchContainer.swift +++ b/ios/MullvadVPN/CustomSwitchContainer.swift @@ -50,8 +50,8 @@ class CustomSwitchContainer: UIView { control.sizeToFit() sizeToFit() - borderShape.cornerRadius = self.bounds.height * 0.5 - borderShape.frame = self.bounds + borderShape.cornerRadius = bounds.height * 0.5 + borderShape.frame = bounds updateBorderOpacity() } @@ -83,5 +83,4 @@ class CustomSwitchContainer: UIView { CATransaction.commit() } - } diff --git a/ios/MullvadVPN/CustomTextField.swift b/ios/MullvadVPN/CustomTextField.swift index 9c400f4720..d6813bfcf2 100644 --- a/ios/MullvadVPN/CustomTextField.swift +++ b/ios/MullvadVPN/CustomTextField.swift @@ -10,7 +10,6 @@ import Foundation import UIKit class CustomTextField: UITextField { - var cornerRadius: CGFloat = UIMetrics.controlCornerRadius { didSet { layer.cornerRadius = cornerRadius @@ -23,7 +22,7 @@ class CustomTextField: UITextField { } } - var placeholderTextColor: UIColor = UIColor.TextField.placeholderTextColor { + var placeholderTextColor = UIColor.TextField.placeholderTextColor { didSet { updatePlaceholderTextColor() } diff --git a/ios/MullvadVPN/CustomTextView.swift b/ios/MullvadVPN/CustomTextView.swift index aed22fe07e..d3dffe8e79 100644 --- a/ios/MullvadVPN/CustomTextView.swift +++ b/ios/MullvadVPN/CustomTextView.swift @@ -11,7 +11,7 @@ import UIKit class CustomTextView: UITextView { private static let textViewCornerRadius: CGFloat = 4 - var roundCorners: Bool = true { + var roundCorners = true { didSet { layer.cornerRadius = roundCorners ? Self.textViewCornerRadius : 0 } @@ -41,7 +41,7 @@ class CustomTextView: UITextView { override var font: UIFont? { didSet { - placeholderTextLabel.font = self.font ?? UIFont.preferredFont(forTextStyle: .body) + placeholderTextLabel.font = font ?? UIFont.preferredFont(forTextStyle: .body) } } @@ -74,7 +74,10 @@ class CustomTextView: UITextView { } get { if roundCorners { - return UIBezierPath(roundedRect: accessibilityFrame, cornerRadius: Self.textViewCornerRadius) + return UIBezierPath( + roundedRect: accessibilityFrame, + cornerRadius: Self.textViewCornerRadius + ) } else { return UIBezierPath(rect: accessibilityFrame) } @@ -97,9 +100,12 @@ class CustomTextView: UITextView { // Create placeholder constraints placeholderConstraints = [ placeholderTextLabel.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), - placeholderTextLabel.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), - placeholderTextLabel.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), - placeholderTextLabel.bottomAnchor.constraint(lessThanOrEqualTo: safeAreaLayoutGuide.bottomAnchor), + placeholderTextLabel.leadingAnchor + .constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + placeholderTextLabel.trailingAnchor + .constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + placeholderTextLabel.bottomAnchor + .constraint(lessThanOrEqualTo: safeAreaLayoutGuide.bottomAnchor), ] NSLayoutConstraint.activate(placeholderConstraints) @@ -117,8 +123,9 @@ class CustomTextView: UITextView { notificationObserver = NotificationCenter.default.addObserver( forName: NSTextStorage.didProcessEditingNotification, object: textStorage, - queue: OperationQueue.main) { [weak self] (note) in - self?.updatePlaceholderVisibility() + queue: OperationQueue.main + ) { [weak self] note in + self?.updatePlaceholderVisibility() } updatePlaceholderVisibility() @@ -158,5 +165,4 @@ class CustomTextView: UITextView { private func updatePlaceholderVisibility() { placeholderTextLabel.isHidden = textStorage.length > 0 } - } diff --git a/ios/MullvadVPN/DNSSettings.swift b/ios/MullvadVPN/DNSSettings.swift index f841b6cb1b..e204a7f60b 100644 --- a/ios/MullvadVPN/DNSSettings.swift +++ b/ios/MullvadVPN/DNSSettings.swift @@ -56,7 +56,7 @@ struct DNSSettings: Codable, Equatable { var blockingOptions: DNSBlockingOptions = [] /// Enable custom DNS. - var enableCustomDNS: Bool = false + var enableCustomDNS = false /// Custom DNS domains. var customDNSDomains: [AnyIPAddress] = [] @@ -82,23 +82,38 @@ struct DNSSettings: Codable, Equatable { let container = try decoder.container(keyedBy: CodingKeys.self) // Added in 2022.1 - if let storedBlockingOptions = try container.decodeIfPresent(DNSBlockingOptions.self, forKey: .blockingOptions) { + if let storedBlockingOptions = try container.decodeIfPresent( + DNSBlockingOptions.self, + forKey: .blockingOptions + ) { blockingOptions = storedBlockingOptions } - if let storedBlockAdvertising = try container.decodeIfPresent(Bool.self, forKey: .blockAdvertising), storedBlockAdvertising { + if let storedBlockAdvertising = try container.decodeIfPresent( + Bool.self, + forKey: .blockAdvertising + ), storedBlockAdvertising { blockingOptions.insert(.blockAdvertising) } - if let storedBlockTracking = try container.decodeIfPresent(Bool.self, forKey: .blockTracking), storedBlockTracking { + if let storedBlockTracking = try container.decodeIfPresent( + Bool.self, + forKey: .blockTracking + ), storedBlockTracking { blockingOptions.insert(.blockTracking) } - if let storedEnableCustomDNS = try container.decodeIfPresent(Bool.self, forKey: .enableCustomDNS) { + if let storedEnableCustomDNS = try container.decodeIfPresent( + Bool.self, + forKey: .enableCustomDNS + ) { enableCustomDNS = storedEnableCustomDNS } - if let storedCustomDNSDomains = try container.decodeIfPresent([AnyIPAddress].self, forKey: .customDNSDomains) { + if let storedCustomDNSDomains = try container.decodeIfPresent( + [AnyIPAddress].self, + forKey: .customDNSDomains + ) { customDNSDomains = storedCustomDNSDomains } } diff --git a/ios/MullvadVPN/DataSourceSnapshot.swift b/ios/MullvadVPN/DataSourceSnapshot.swift index 19b6043aa1..6e19708776 100644 --- a/ios/MullvadVPN/DataSourceSnapshot.swift +++ b/ios/MullvadVPN/DataSourceSnapshot.swift @@ -48,7 +48,10 @@ struct DataSourceSnapshot<Section: Hashable, Item: Hashable> { let newItemRange = (itemRange.startIndex ..< newEndIndex) sectionToItemMapping[sectionIndex] = newItemRange - orderedItems.insert(uniqueItemsToAppend.array, at: IndexSet(integersIn: oldEndIndex..<newEndIndex)) + orderedItems.insert( + uniqueItemsToAppend.array, + at: IndexSet(integersIn: oldEndIndex ..< newEndIndex) + ) offsetItemRange(inSectionsAfter: sectionIndex, by: uniqueItemsToAppend.count) } @@ -56,8 +59,8 @@ struct DataSourceSnapshot<Section: Hashable, Item: Hashable> { mutating func appendSections(_ newSections: [Section]) { let lastSectionRange = sectionToItemMapping.last let emptyRange = lastSectionRange.flatMap { range in - return (range.upperBound..<range.upperBound) - } ?? (0..<0) + return (range.upperBound ..< range.upperBound) + } ?? (0 ..< 0) let uniqueNewSections = NSOrderedSet(array: newSections) @@ -193,21 +196,21 @@ extension DataSourceSnapshot { var debugDescription: String { switch self { - case .insert(let indexPath): + case let .insert(indexPath): return "insert \(indexPath)" - case .delete(let indexPath): + case let .delete(indexPath): return "delete \(indexPath)" - case .move(let source, let target): + case let .move(source, target): return "move from \(source) to \(target)" - case .reload(let indexPath): + case let .reload(indexPath): return "reload \(indexPath)" - case .reconfigure(let indexPath): + case let .reconfigure(indexPath): return "reconfigure \(indexPath)" } } func breakMoveOntoInsertionDeletion() -> [Change] { - if case .move(let fromIndexPath, let toIndexPath) = self { + if case let .move(fromIndexPath, toIndexPath) = self { return [.delete(fromIndexPath), .insert(toIndexPath)] } else { return [self] @@ -233,7 +236,7 @@ extension DataSourceSnapshot { // Guard against recording the `.move` twice when exchanging two adjacent items. let isSwappingTwoAdjacentItems = changes.contains { otherChange in - if case .move(let fromIndexPath, let toIndexPath) = otherChange { + if case let .move(fromIndexPath, toIndexPath) = otherChange { let itemDistance = abs(oldIndexPath.row - fromIndexPath.row) return oldIndexPath == toIndexPath && newIndexPath == fromIndexPath && @@ -291,29 +294,34 @@ extension DataSourceSnapshot { .sorted(by: Self.changeSortPredicate) for sourceChange in changes { - guard case .move(let sourceIndexPath, let targetIndexPath) = sourceChange else { + guard case let .move(sourceIndexPath, targetIndexPath) = sourceChange else { newChanges.append(sourceChange) continue } // Replay all changes to compute the item's index path, ignoring the changes associated with the current // change. - let inferredIndexPath = sortedChangesWithoutMoves.reduce(into: sourceIndexPath) { inferredIndexPath, otherChange in - switch otherChange { - case .insert(let insertedIndexPath) where insertedIndexPath != targetIndexPath: - if inferredIndexPath.row >= insertedIndexPath.row, inferredIndexPath.section == insertedIndexPath.section { - inferredIndexPath.row += 1 - } + let inferredIndexPath = sortedChangesWithoutMoves + .reduce(into: sourceIndexPath) { inferredIndexPath, otherChange in + switch otherChange { + case let .insert(insertedIndexPath) where insertedIndexPath != targetIndexPath: + if inferredIndexPath.row >= insertedIndexPath.row, + inferredIndexPath.section == insertedIndexPath.section + { + inferredIndexPath.row += 1 + } - case .delete(let deletedIndexPath) where deletedIndexPath != sourceIndexPath: - if inferredIndexPath.row > deletedIndexPath.row, inferredIndexPath.section == deletedIndexPath.section { - inferredIndexPath.row -= 1 - } + case let .delete(deletedIndexPath) where deletedIndexPath != sourceIndexPath: + if inferredIndexPath.row > deletedIndexPath.row, + inferredIndexPath.section == deletedIndexPath.section + { + inferredIndexPath.row -= 1 + } - default: - break + default: + break + } } - } // Discard the change if the index path, produced after replaying other changes, matches the target index // path. @@ -333,19 +341,19 @@ extension DataSourceSnapshot { /// Reload, reconfigure: ascending private static func changeSortPredicate(_ lhs: Change, _ rhs: Change) -> Bool { switch (lhs, rhs) { - case (.insert(let lhsIndexPath), .insert(let rhsIndexPath)): + case let (.insert(lhsIndexPath), .insert(rhsIndexPath)): return lhsIndexPath < rhsIndexPath - case (.delete(let lhsIndexPath), .delete(let rhsIndexPath)): + case let (.delete(lhsIndexPath), .delete(rhsIndexPath)): return lhsIndexPath > rhsIndexPath - case (.reload(let lhsIndexPath), .reload(let rhsIndexPath)): + case let (.reload(lhsIndexPath), .reload(rhsIndexPath)): return lhsIndexPath < rhsIndexPath - case (.reconfigure(let lhsIndexPath), .reconfigure(let rhsIndexPath)): + case let (.reconfigure(lhsIndexPath), .reconfigure(rhsIndexPath)): return lhsIndexPath < rhsIndexPath - case (let lhs, let rhs): + case let (lhs, rhs): return lhs.sortOrder < rhs.sortOrder } } @@ -358,20 +366,20 @@ extension DataSourceSnapshot { for change in changes { switch change { - case .insert(let indexPath): + case let .insert(indexPath): indexPathsToInsert.append(indexPath) - case .delete(let indexPath): + case let .delete(indexPath): indexPathsToDelete.append(indexPath) case .move: // Moves are broken down onto insert and delete changes at this point. break - case .reload(let indexPath): + case let .reload(indexPath): indexPathsToReload.append(indexPath) - case .reconfigure(let indexPath): + case let .reconfigure(indexPath): indexPathsToReconfigure.append(indexPath) } } @@ -425,7 +433,11 @@ struct DataSnapshotDifference: CustomDebugStringConvertible { return s } - func apply(to tableView: UITableView, animateDifferences: Bool, completion: ((Bool) -> Void)? = nil) { + func apply( + to tableView: UITableView, + animateDifferences: Bool, + completion: ((Bool) -> Void)? = nil + ) { let animation: UITableView.RowAnimation = animateDifferences ? .automatic : .none tableView.performBatchUpdates({ @@ -456,8 +468,7 @@ struct DataSnapshotDifference: CustomDebugStringConvertible { configuration: StackViewApplyDataSnapshotConfiguration, animateDifferences: Bool, completion: ((Bool) -> Void)? = nil - ) - { + ) { let viewsToRemove = indexPathsToDelete.map { indexPath in return stackView.arrangedSubviews[indexPath.row] } diff --git a/ios/MullvadVPN/DeviceManagementContentView.swift b/ios/MullvadVPN/DeviceManagementContentView.swift index f62013bef0..abff4306b2 100644 --- a/ios/MullvadVPN/DeviceManagementContentView.swift +++ b/ios/MullvadVPN/DeviceManagementContentView.swift @@ -83,7 +83,7 @@ class DeviceManagementContentView: UIView { return stackView }() - var canContinue: Bool = false { + var canContinue = false { didSet { updateView() } @@ -133,7 +133,7 @@ class DeviceManagementContentView: UIView { spacer.setContentCompressionResistancePriority(.defaultLow, for: .vertical) let subviewsToAdd = [ - statusImageView, titleLabel, messageLabel, deviceStackView, spacer, buttonStackView + statusImageView, titleLabel, messageLabel, deviceStackView, spacer, buttonStackView, ] for subview in subviewsToAdd { addSubview(subview) @@ -168,7 +168,7 @@ class DeviceManagementContentView: UIView { buttonStackView.topAnchor.constraint(equalTo: spacer.bottomAnchor), buttonStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), buttonStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - buttonStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + buttonStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), ]) } @@ -214,9 +214,9 @@ class DeviceManagementContentView: UIView { "LOGOUT_DEVICES_MESSAGE", tableName: "DeviceManagement", value: """ - Please log out of at least one by removing it from the list below. You can find \ - the corresponding device name under the device’s Account settings. - """, + Please log out of at least one by removing it from the list below. You can find \ + the corresponding device name under the device’s Account settings. + """, comment: "" ) } diff --git a/ios/MullvadVPN/DeviceManagementInteractor.swift b/ios/MullvadVPN/DeviceManagementInteractor.swift index 08d7d744be..1890799221 100644 --- a/ios/MullvadVPN/DeviceManagementInteractor.swift +++ b/ios/MullvadVPN/DeviceManagementInteractor.swift @@ -17,7 +17,10 @@ class DeviceManagementInteractor { } @discardableResult - func getDevices(_ completionHandler: @escaping (OperationCompletion<[REST.Device], Error>) -> Void) -> Cancellable { + func getDevices( + _ completionHandler: @escaping (OperationCompletion<[REST.Device], Error>) + -> Void + ) -> Cancellable { return devicesProxy.getDevices( accountNumber: accountNumber, retryStrategy: .default @@ -27,7 +30,10 @@ class DeviceManagementInteractor { } @discardableResult - func deleteDevice(_ identifier: String, completionHandler: @escaping (OperationCompletion<Bool, Error>) -> Void) -> Cancellable { + func deleteDevice( + _ identifier: String, + completionHandler: @escaping (OperationCompletion<Bool, Error>) -> Void + ) -> Cancellable { return devicesProxy.deleteDevice( accountNumber: accountNumber, identifier: identifier, diff --git a/ios/MullvadVPN/DeviceManagementViewController.swift b/ios/MullvadVPN/DeviceManagementViewController.swift index 9db683cff4..128e1c6ab4 100644 --- a/ios/MullvadVPN/DeviceManagementViewController.swift +++ b/ios/MullvadVPN/DeviceManagementViewController.swift @@ -6,8 +6,8 @@ // Copyright © 2022 Mullvad VPN AB. All rights reserved. // -import UIKit import Logging +import UIKit protocol DeviceManagementViewControllerDelegate: AnyObject { func deviceManagementViewControllerDidFinish(_ controller: DeviceManagementViewController) @@ -94,7 +94,10 @@ class DeviceManagementViewController: UIViewController, RootContainment { ]) } - func fetchDevices(animateUpdates: Bool, completionHandler: ((OperationCompletion<Void, Error>) -> Void)? = nil) { + func fetchDevices( + animateUpdates: Bool, + completionHandler: ((OperationCompletion<Void, Error>) -> Void)? = nil + ) { interactor.getDevices { [weak self] completion in guard let self = self else { return } @@ -151,7 +154,7 @@ class DeviceManagementViewController: UIViewController, RootContainment { } private func getErrorDescription(_ error: Error) -> String { - if case .network(let urlError) = error as? REST.Error { + if case let .network(urlError) = error as? REST.Error { return urlError.localizedDescription } else { return error.localizedDescription @@ -223,7 +226,7 @@ class DeviceManagementViewController: UIViewController, RootContainment { handler: { _ in completion(true) } - ) + ), ] for action in actions { @@ -243,7 +246,7 @@ class DeviceManagementViewController: UIViewController, RootContainment { completionHandler(completion.error) } - case .failure(let error): + case let .failure(error): self.logger.error( chainedError: AnyChainedError(error), message: "Failed to delete device." diff --git a/ios/MullvadVPN/DeviceRowView.swift b/ios/MullvadVPN/DeviceRowView.swift index bca1830ccd..c35f2f78db 100644 --- a/ios/MullvadVPN/DeviceRowView.swift +++ b/ios/MullvadVPN/DeviceRowView.swift @@ -45,7 +45,7 @@ class DeviceRowView: UIView { return button }() - var showsActivityIndicator: Bool = false { + var showsActivityIndicator = false { didSet { removeButton.isHidden = showsActivityIndicator diff --git a/ios/MullvadVPN/DisconnectSplitButton.swift b/ios/MullvadVPN/DisconnectSplitButton.swift index 4a7f18a369..59e807cfa0 100644 --- a/ios/MullvadVPN/DisconnectSplitButton.swift +++ b/ios/MullvadVPN/DisconnectSplitButton.swift @@ -10,10 +10,9 @@ import Foundation import UIKit class DisconnectSplitButton: UIView { - private var secondaryButtonSize: CGSize { // TODO: make it less hardcoded - switch self.traitCollection.userInterfaceIdiom { + switch traitCollection.userInterfaceIdiom { case .phone: return CGSize(width: 42, height: 42) case .pad: @@ -42,11 +41,15 @@ class DisconnectSplitButton: UIView { stackView.alignment = .fill stackView.spacing = 1 - secondaryButton.setImage(UIImage(named: "IconReload")?.imageFlippedForRightToLeftLayoutDirection(), for: .normal) + secondaryButton.setImage( + UIImage(named: "IconReload")?.imageFlippedForRightToLeftLayoutDirection(), + for: .normal + ) primaryButton.overrideContentEdgeInsets = true secondaryButtonWidthConstraint = secondaryButton.widthAnchor.constraint(equalToConstant: 0) - secondaryButtonHeightConstraint = secondaryButton.heightAnchor.constraint(equalToConstant: 0) + secondaryButtonHeightConstraint = secondaryButton.heightAnchor + .constraint(equalToConstant: 0) super.init(frame: .zero) @@ -59,7 +62,7 @@ class DisconnectSplitButton: UIView { stackView.bottomAnchor.constraint(equalTo: bottomAnchor), secondaryButtonWidthConstraint, - secondaryButtonHeightConstraint + secondaryButtonHeightConstraint, ]) updateTraitConstraints() @@ -78,7 +81,7 @@ class DisconnectSplitButton: UIView { } private func updateTraitConstraints() { - let newSize = self.secondaryButtonSize + let newSize = secondaryButtonSize secondaryButtonWidthConstraint.constant = newSize.width secondaryButtonHeightConstraint.constant = newSize.height adjustTitleLabelPosition() @@ -87,7 +90,7 @@ class DisconnectSplitButton: UIView { private func adjustTitleLabelPosition() { var contentInsets = primaryButton.defaultContentInsets - let offset = stackView.spacing + self.secondaryButtonSize.width + let offset = stackView.spacing + secondaryButtonSize.width if case .leftToRight = effectiveUserInterfaceLayoutDirection { contentInsets.left = offset diff --git a/ios/MullvadVPN/DisplayChainedError.swift b/ios/MullvadVPN/DisplayChainedError.swift index cb0ab32a9d..15dcf930ce 100644 --- a/ios/MullvadVPN/DisplayChainedError.swift +++ b/ios/MullvadVPN/DisplayChainedError.swift @@ -16,7 +16,7 @@ protocol DisplayChainedError { extension REST.Error: DisplayChainedError { var errorChainDescription: String? { switch self { - case .network(let urlError): + case let .network(urlError): return String( format: NSLocalizedString( "NETWORK_ERROR", @@ -26,7 +26,7 @@ extension REST.Error: DisplayChainedError { ), urlError.localizedDescription ) - case .unhandledResponse(let statusCode, let serverResponse): + case let .unhandledResponse(statusCode, serverResponse): return String( format: NSLocalizedString( "SERVER_ERROR", @@ -57,7 +57,7 @@ extension REST.Error: DisplayChainedError { extension SKError: LocalizedError { public var errorDescription: String? { - switch self.code { + switch code { case .unknown: return NSLocalizedString( "UNKNOWN_ERROR", @@ -94,7 +94,7 @@ extension SKError: LocalizedError { comment: "" ) default: - return self.localizedDescription + return localizedDescription } } } @@ -110,7 +110,7 @@ extension AppStorePaymentManager.Error: DisplayChainedError { comment: "" ) - case .validateAccount(let restError): + case let .validateAccount(restError): let reason = restError.errorChainDescription ?? "" if restError.compareErrorCode(.invalidAccount) { @@ -135,10 +135,11 @@ extension AppStorePaymentManager.Error: DisplayChainedError { ) } - case .readReceipt(let readReceiptError): + case let .readReceipt(readReceiptError): switch readReceiptError { - case .refresh(let storeError): - let skErrorMessage = (storeError as? SKError)?.errorDescription ?? storeError.localizedDescription + case let .refresh(storeError): + let skErrorMessage = (storeError as? SKError)?.errorDescription ?? storeError + .localizedDescription return String( format: NSLocalizedString( @@ -149,7 +150,7 @@ extension AppStorePaymentManager.Error: DisplayChainedError { ), skErrorMessage ) - case .io(let ioError): + case let .io(ioError): return String( format: NSLocalizedString( "READ_RECEIPT_ERROR", @@ -168,7 +169,7 @@ extension AppStorePaymentManager.Error: DisplayChainedError { ) } - case .sendReceipt(let restError): + case let .sendReceipt(restError): let reason = restError.errorChainDescription ?? "" let errorFormat = NSLocalizedString( "SEND_RECEIPT_ERROR", @@ -187,7 +188,7 @@ extension AppStorePaymentManager.Error: DisplayChainedError { errorString.append(recoverySuggestion) return errorString - case .storePayment(let storeError): + case let .storePayment(storeError): return (storeError as? SKError)?.errorDescription ?? storeError.localizedDescription } } diff --git a/ios/MullvadVPN/EmptyTableViewHeaderFooterView.swift b/ios/MullvadVPN/EmptyTableViewHeaderFooterView.swift index 78c30b5e15..46b88f78ed 100644 --- a/ios/MullvadVPN/EmptyTableViewHeaderFooterView.swift +++ b/ios/MullvadVPN/EmptyTableViewHeaderFooterView.swift @@ -9,13 +9,12 @@ import UIKit class EmptyTableViewHeaderFooterView: UITableViewHeaderFooterView { - override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) - self.textLabel?.isHidden = true - self.contentView.backgroundColor = .clear - self.backgroundView?.backgroundColor = .clear + textLabel?.isHidden = true + contentView.backgroundColor = .clear + backgroundView?.backgroundColor = .clear } required init?(coder: NSCoder) { diff --git a/ios/MullvadVPN/GeoJSON.swift b/ios/MullvadVPN/GeoJSON.swift index cbc9c9f3b9..c33fab801b 100644 --- a/ios/MullvadVPN/GeoJSON.swift +++ b/ios/MullvadVPN/GeoJSON.swift @@ -6,8 +6,8 @@ // Copyright © 2021 Mullvad VPN AB. All rights reserved. // -import Foundation import CoreLocation +import Foundation import MapKit enum GeoJSON {} @@ -32,15 +32,15 @@ extension GeoJSON { } var mkOverlays: [MKOverlay] { - return features.flatMap { (feature) -> [MKOverlay] in + return features.flatMap { feature -> [MKOverlay] in // Some tools like mapshaper output empty features after optimizing out the geometry guard let geometry = feature.geometry else { return [] } switch geometry { - case .polygon(let polygon): + case let .polygon(polygon): return polygon.mkPolygons - case .multiPolygon(let multiPolygon): + case let .multiPolygon(multiPolygon): return multiPolygon.mkPolygons } } @@ -90,7 +90,11 @@ extension GeoJSON { self = .multiPolygon(try decoder.singleValueContainer().decode(MultiPolygon.self)) default: - throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Geometry: Unknown type \(type)") + throw DecodingError.dataCorruptedError( + forKey: .type, + in: container, + debugDescription: "Geometry: Unknown type \(type)" + ) } } @@ -103,7 +107,7 @@ extension GeoJSON { let coordinates: [[[Double]]] var mkPolygons: [MKPolygon] { - let coords = self.geoCoordinates + let coords = geoCoordinates let exteriorCoordinates = coords.first ?? [] let exteriorPolygon = MKPolygon( @@ -125,7 +129,10 @@ extension GeoJSON { private var geoCoordinates: [[CLLocationCoordinate2D]] { return coordinates.map { values -> [CLLocationCoordinate2D] in return values.map { coordinates -> CLLocationCoordinate2D in - return CLLocationCoordinate2D(latitude: coordinates[1], longitude: coordinates[0]) + return CLLocationCoordinate2D( + latitude: coordinates[1], + longitude: coordinates[0] + ) } } } diff --git a/ios/MullvadVPN/HeaderBarView.swift b/ios/MullvadVPN/HeaderBarView.swift index e7527f2f18..714c9bdab8 100644 --- a/ios/MullvadVPN/HeaderBarView.swift +++ b/ios/MullvadVPN/HeaderBarView.swift @@ -82,17 +82,35 @@ class HeaderBarView: UIView { logoImageView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), logoImageView.centerYAnchor.constraint(equalTo: brandNameImageView.centerYAnchor), logoImageView.widthAnchor.constraint(equalToConstant: 44), - logoImageView.heightAnchor.constraint(equalTo: logoImageView.widthAnchor, multiplier: 1), + logoImageView.heightAnchor.constraint( + equalTo: logoImageView.widthAnchor, + multiplier: 1 + ), - brandNameImageView.leadingAnchor.constraint(equalTo: logoImageView.trailingAnchor, constant: 9), - brandNameImageView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 22), - brandNameImageView.widthAnchor.constraint(equalTo: brandNameImageView.heightAnchor, multiplier: brandNameAspectRatio), + brandNameImageView.leadingAnchor.constraint( + equalTo: logoImageView.trailingAnchor, + constant: 9 + ), + brandNameImageView.topAnchor.constraint( + equalTo: layoutMarginsGuide.topAnchor, + constant: 22 + ), + brandNameImageView.widthAnchor.constraint( + equalTo: brandNameImageView.heightAnchor, + multiplier: brandNameAspectRatio + ), brandNameImageView.heightAnchor.constraint(equalToConstant: 18), - layoutMarginsGuide.bottomAnchor.constraint(equalTo: brandNameImageView.bottomAnchor, constant: 22), + layoutMarginsGuide.bottomAnchor.constraint( + equalTo: brandNameImageView.bottomAnchor, + constant: 22 + ), - settingsButton.leadingAnchor.constraint(greaterThanOrEqualTo: brandNameImageView.trailingAnchor, constant: 8), + settingsButton.leadingAnchor.constraint( + greaterThanOrEqualTo: brandNameImageView.trailingAnchor, + constant: 8 + ), settingsButton.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - settingsButton.centerYAnchor.constraint(equalTo: brandNameImageView.centerYAnchor) + settingsButton.centerYAnchor.constraint(equalTo: brandNameImageView.centerYAnchor), ] [logoImageView, brandNameImageView, settingsButton].forEach { addSubview($0) } diff --git a/ios/MullvadVPN/IPAddress+Codable.swift b/ios/MullvadVPN/IPAddress+Codable.swift index d8802e85ca..2aafd53726 100644 --- a/ios/MullvadVPN/IPAddress+Codable.swift +++ b/ios/MullvadVPN/IPAddress+Codable.swift @@ -16,7 +16,10 @@ extension IPv4Address: Codable { if let decoded = IPv4Address(ipString) { self = decoded } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid IPv4 representation") + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Invalid IPv4 representation" + ) } } @@ -35,7 +38,10 @@ extension IPv6Address: Codable { if let decoded = IPv6Address(ipString) { self = decoded } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid IPv6 representation") + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Invalid IPv6 representation" + ) } } diff --git a/ios/MullvadVPN/IPEndpoint.swift b/ios/MullvadVPN/IPEndpoint.swift index 31f2089a07..76d213192c 100644 --- a/ios/MullvadVPN/IPEndpoint.swift +++ b/ios/MullvadVPN/IPEndpoint.swift @@ -7,9 +7,9 @@ // import Foundation +import protocol Network.IPAddress import struct Network.IPv4Address import struct Network.IPv6Address -import protocol Network.IPAddress struct IPv4Endpoint: Hashable, Equatable, Codable, CustomStringConvertible { let ip: IPv4Address @@ -21,9 +21,15 @@ struct IPv4Endpoint: Hashable, Equatable, Codable, CustomStringConvertible { } init?<S>(string: S) where S: StringProtocol { - let components = string.split(separator: ":", maxSplits: 2, omittingEmptySubsequences: false) + let components = string.split( + separator: ":", + maxSplits: 2, + omittingEmptySubsequences: false + ) - if components.count == 2, let parsedIP = IPv4Address(String(components[0])), let parsedPort = UInt16(components[1]) { + if components.count == 2, let parsedIP = IPv4Address(String(components[0])), + let parsedPort = UInt16(components[1]) + { ip = parsedIP port = parsedPort } else { @@ -38,7 +44,10 @@ struct IPv4Endpoint: Hashable, Equatable, Codable, CustomStringConvertible { if let parsedAddress = IPv4Endpoint(string: string) { self = parsedAddress } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot parse the IPv4 endpoint") + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Cannot parse the IPv4 endpoint" + ) } } @@ -75,7 +84,7 @@ struct IPv6Endpoint: Hashable, Equatable, Codable, CustomStringConvertible { let addressString = string[..<lastColon] let portString = string[portIndex...] - guard addressString.first == "[" && addressString.last == "]" else { + guard addressString.first == "[", addressString.last == "]" else { return nil } @@ -96,7 +105,10 @@ struct IPv6Endpoint: Hashable, Equatable, Codable, CustomStringConvertible { if let parsedAddress = IPv6Endpoint(string: string) { self = parsedAddress } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot parse the IPv6 endpoint") + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Cannot parse the IPv6 endpoint" + ) } } @@ -121,18 +133,18 @@ enum AnyIPEndpoint: Hashable, Equatable, Codable, CustomStringConvertible { var ip: IPAddress { switch self { - case .ipv4(let ipv4Endpoint): + case let .ipv4(ipv4Endpoint): return ipv4Endpoint.ip - case .ipv6(let ipv6Endpoint): + case let .ipv6(ipv6Endpoint): return ipv6Endpoint.ip } } var port: UInt16 { switch self { - case .ipv4(let ipv4Endpoint): + case let .ipv4(ipv4Endpoint): return ipv4Endpoint.port - case .ipv6(let ipv6Endpoint): + case let .ipv6(ipv6Endpoint): return ipv6Endpoint.port } } @@ -156,7 +168,10 @@ enum AnyIPEndpoint: Hashable, Equatable, Codable, CustomStringConvertible { } else if let ipv6Endpoint = IPv6Endpoint(string: string) { self = .ipv6(ipv6Endpoint) } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot parse the endpoint") + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Cannot parse the endpoint" + ) } } @@ -168,19 +183,19 @@ enum AnyIPEndpoint: Hashable, Equatable, Codable, CustomStringConvertible { var description: String { switch self { - case .ipv4(let ipv4Endpoint): + case let .ipv4(ipv4Endpoint): return "\(ipv4Endpoint)" - case .ipv6(let ipv6Endpoint): + case let .ipv6(ipv6Endpoint): return "\(ipv6Endpoint)" } } static func == (lhs: AnyIPEndpoint, rhs: AnyIPEndpoint) -> Bool { switch (lhs, rhs) { - case (.ipv4(let lhsEndpoint), .ipv4(let rhsEndpoint)): + case let (.ipv4(lhsEndpoint), .ipv4(rhsEndpoint)): return lhsEndpoint == rhsEndpoint - case (.ipv6(let lhsEndpoint), .ipv6(let rhsEndpoint)): + case let (.ipv6(lhsEndpoint), .ipv6(rhsEndpoint)): return lhsEndpoint == rhsEndpoint default: diff --git a/ios/MullvadVPN/InAppPurchaseButton.swift b/ios/MullvadVPN/InAppPurchaseButton.swift index 483ccfdf4a..a87f277536 100644 --- a/ios/MullvadVPN/InAppPurchaseButton.swift +++ b/ios/MullvadVPN/InAppPurchaseButton.swift @@ -10,10 +10,9 @@ import Foundation import UIKit class InAppPurchaseButton: AppButton { - let activityIndicator = SpinnerActivityIndicatorView(style: .medium) - var isLoading: Bool = false { + var isLoading = false { didSet { if isLoading { activityIndicator.startAnimating() @@ -51,7 +50,7 @@ class InAppPurchaseButton: AppButton { override func titleRect(forContentRect contentRect: CGRect) -> CGRect { var titleRect = super.titleRect(forContentRect: contentRect) - let activityIndicatorRect = self.activityIndicatorRect(forContentRect: contentRect) + let activityIndicatorRect = activityIndicatorRect(forContentRect: contentRect) // Adjust the title frame in case if it overlaps the activity indicator let intersection = titleRect.intersection(activityIndicatorRect) diff --git a/ios/MullvadVPN/IntentHandlers.swift b/ios/MullvadVPN/IntentHandlers.swift index 8764df7ef9..36ad4b3bd6 100644 --- a/ios/MullvadVPN/IntentHandlers.swift +++ b/ios/MullvadVPN/IntentHandlers.swift @@ -12,7 +12,8 @@ final class StartVPNIntentHandler: NSObject, StartVPNIntentHandling { func handle(intent: StartVPNIntent, completion: @escaping (StartVPNIntentResponse) -> Void) { TunnelManager.shared.startTunnel { operationCompletion in let code: StartVPNIntentResponseCode = operationCompletion.isSuccess - ? .success : .failure + ? .success + : .failure let response = StartVPNIntentResponse(code: code, userActivity: nil) completion(response) @@ -24,7 +25,8 @@ final class StopVPNIntentHandler: NSObject, StopVPNIntentHandling { func handle(intent: StopVPNIntent, completion: @escaping (StopVPNIntentResponse) -> Void) { TunnelManager.shared.stopTunnel { operationCompletion in let code: StopVPNIntentResponseCode = operationCompletion.isSuccess - ? .success : .failure + ? .success + : .failure let response = StopVPNIntentResponse(code: code, userActivity: nil) completion(response) @@ -33,7 +35,10 @@ final class StopVPNIntentHandler: NSObject, StopVPNIntentHandling { } final class ReconnectVPNIntentHandler: NSObject, ReconnectVPNIntentHandling { - func handle(intent: ReconnectVPNIntent, completion: @escaping (ReconnectVPNIntentResponse) -> Void) { + func handle( + intent: ReconnectVPNIntent, + completion: @escaping (ReconnectVPNIntentResponse) -> Void + ) { let tunnelManager = TunnelManager.shared tunnelManager.reconnectTunnel(selectNewRelay: true) { operationCompletion in diff --git a/ios/MullvadVPN/LaunchViewController.swift b/ios/MullvadVPN/LaunchViewController.swift index f108d8f6e4..02218244d5 100644 --- a/ios/MullvadVPN/LaunchViewController.swift +++ b/ios/MullvadVPN/LaunchViewController.swift @@ -9,7 +9,6 @@ import UIKit class LaunchViewController: UIViewController { - override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } @@ -28,7 +27,7 @@ class LaunchViewController: UIViewController { initialController.didMove(toParent: self) NSLayoutConstraint.activate([ - initialController.view.topAnchor.constraint(equalTo: view.topAnchor), + initialController.view.topAnchor.constraint(equalTo: view.topAnchor), initialController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), initialController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), initialController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), diff --git a/ios/MullvadVPN/Location.swift b/ios/MullvadVPN/Location.swift index 1d10f4c86e..ae8566a6e1 100644 --- a/ios/MullvadVPN/Location.swift +++ b/ios/MullvadVPN/Location.swift @@ -6,8 +6,8 @@ // Copyright © 2020 Mullvad VPN AB. All rights reserved. // -import Foundation import struct CoreLocation.CLLocationCoordinate2D +import Foundation struct Location: Codable, Equatable { var country: String diff --git a/ios/MullvadVPN/LocationDataSource.swift b/ios/MullvadVPN/LocationDataSource.swift index 6f7d922eb3..99f584e7b7 100644 --- a/ios/MullvadVPN/LocationDataSource.swift +++ b/ios/MullvadVPN/LocationDataSource.swift @@ -19,12 +19,12 @@ protocol LocationDataSourceItemProtocol { } class LocationDataSource: NSObject, UITableViewDataSource { - private var nodeByLocation = [RelayLocation: Node]() private var locationList = [RelayLocation]() private var rootNode = makeRootNode() - typealias CellProviderBlock = (UITableView, IndexPath, LocationDataSourceItemProtocol) -> UITableViewCell? + typealias CellProviderBlock = (UITableView, IndexPath, LocationDataSourceItemProtocol) + -> UITableViewCell? private let tableView: UITableView private let cellProvider: CellProviderBlock @@ -52,10 +52,16 @@ class LocationDataSource: NSObject, UITableViewDataSource { tableView.dataSource = self } - func setSelectedRelayLocation(_ relayLocation: RelayLocation?, showHiddenParents: Bool, animated: Bool, scrollPosition: UITableView.ScrollPosition, completion: (() -> Void)? = nil) { - self.selectedRelayLocation = relayLocation - self.lastShowHiddenParents = showHiddenParents - self.lastScrollPosition = scrollPosition + func setSelectedRelayLocation( + _ relayLocation: RelayLocation?, + showHiddenParents: Bool, + animated: Bool, + scrollPosition: UITableView.ScrollPosition, + completion: (() -> Void)? = nil + ) { + selectedRelayLocation = relayLocation + lastShowHiddenParents = showHiddenParents + lastScrollPosition = scrollPosition if relayLocation == nil { if let indexPath = tableView.indexPathForSelectedRow { @@ -65,7 +71,11 @@ class LocationDataSource: NSObject, UITableViewDataSource { } else { let setSelection = { if let indexPath = self.indexPathForSelectedRelay() { - self.tableView.selectRow(at: indexPath, animated: animated, scrollPosition: scrollPosition) + self.tableView.selectRow( + at: indexPath, + animated: animated, + scrollPosition: scrollPosition + ) } completion?() } @@ -78,15 +88,17 @@ class LocationDataSource: NSObject, UITableViewDataSource { } } - func setRelays(_ response: REST.ServerRelaysResponse) { let rootNode = Self.makeRootNode() var nodeByLocation = [RelayLocation: Node]() let dataSourceWasEmpty = locationList.isEmpty for relay in response.wireguard.relays { - guard case .city(let countryCode, let cityCode) = RelayLocation(dashSeparatedString: relay.location), - let serverLocation = response.locations[relay.location] else { continue } + guard case let .city( + countryCode, + cityCode + ) = RelayLocation(dashSeparatedString: relay.location), + let serverLocation = response.locations[relay.location] else { continue } let relayLocation = RelayLocation.hostname(countryCode, cityCode, relay.hostname) @@ -96,7 +108,8 @@ class LocationDataSource: NSObject, UITableViewDataSource { } // Maintain the `showsChildren` state when transitioning between relay lists - let wasShowingChildren = self.nodeByLocation[ascendantOrSelf]?.showsChildren ?? false + let wasShowingChildren = self.nodeByLocation[ascendantOrSelf]? + .showsChildren ?? false let node: Node switch ascendantOrSelf { @@ -111,7 +124,7 @@ class LocationDataSource: NSObject, UITableViewDataSource { ) rootNode.addChild(node) - case .city(let countryCode, _): + case let .city(countryCode, _): node = Node( type: .city, location: ascendantOrSelf, @@ -122,7 +135,7 @@ class LocationDataSource: NSObject, UITableViewDataSource { ) nodeByLocation[.country(countryCode)]!.addChild(node) - case .hostname(let countryCode, let cityCode, _): + case let .hostname(countryCode, cityCode, _): node = Node( type: .relay, location: ascendantOrSelf, @@ -142,33 +155,42 @@ class LocationDataSource: NSObject, UITableViewDataSource { rootNode.computeActiveChildrenRecursive() self.nodeByLocation = nodeByLocation self.rootNode = rootNode - self.locationList = rootNode.flatRelayLocationList() + locationList = rootNode.flatRelayLocationList() tableView.reloadData() let setSelection = { (_ scrollPosition: UITableView.ScrollPosition) in if let indexPath = self.indexPathForSelectedRelay() { - self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: scrollPosition) + self.tableView.selectRow( + at: indexPath, + animated: false, + scrollPosition: scrollPosition + ) } } // Sometimes the selected relay may be set before the data source is populated with relays. // In that case restore the selection using cached parameters from the last call to // `setSelectedRelayLocation`. - if let selectedRelayLocation = self.selectedRelayLocation, dataSourceWasEmpty { + if let selectedRelayLocation = selectedRelayLocation, dataSourceWasEmpty { if lastShowHiddenParents { showParents(selectedRelayLocation, animated: false) { setSelection(self.lastScrollPosition) } } else { - setSelection(self.lastScrollPosition) + setSelection(lastScrollPosition) } } else { setSelection(.none) } } - func showChildren(_ relayLocation: RelayLocation, showHiddenParents: Bool = false, animated: Bool, completion: (() -> Void)? = nil) { + func showChildren( + _ relayLocation: RelayLocation, + showHiddenParents: Bool = false, + animated: Bool, + completion: (() -> Void)? = nil + ) { toggleChildrenInternal( relayLocation, show: true, @@ -178,7 +200,11 @@ class LocationDataSource: NSObject, UITableViewDataSource { ) } - func hideChildren(_ relayLocation: RelayLocation, animated: Bool, completion: (() -> Void)? = nil) { + func hideChildren( + _ relayLocation: RelayLocation, + animated: Bool, + completion: (() -> Void)? = nil + ) { toggleChildrenInternal( relayLocation, show: false, @@ -188,28 +214,60 @@ class LocationDataSource: NSObject, UITableViewDataSource { ) } - func toggleChildren(_ relayLocation: RelayLocation, animated: Bool, completion: (() -> Void)? = nil) { - guard let node = self.nodeByLocation[relayLocation] else { return } + func toggleChildren( + _ relayLocation: RelayLocation, + animated: Bool, + completion: (() -> Void)? = nil + ) { + guard let node = nodeByLocation[relayLocation] else { return } - toggleChildrenInternal(relayLocation, show: !node.showsChildren, showHiddenParents: false, animated: animated, completion: completion) + toggleChildrenInternal( + relayLocation, + show: !node.showsChildren, + showHiddenParents: false, + animated: animated, + completion: completion + ) } - private func showParents(_ relayLocation: RelayLocation, animated: Bool, completion: (() -> Void)? = nil) { + private func showParents( + _ relayLocation: RelayLocation, + animated: Bool, + completion: (() -> Void)? = nil + ) { switch relayLocation { case .country: completion?() case .city: if let countryLocation = relayLocation.ascendants.first { - toggleChildrenInternal(countryLocation, show: true, showHiddenParents: false, animated: animated, completion: completion) + toggleChildrenInternal( + countryLocation, + show: true, + showHiddenParents: false, + animated: animated, + completion: completion + ) } case .hostname: if let cityLocation = relayLocation.ascendants.last { - toggleChildrenInternal(cityLocation, show: true, showHiddenParents: true, animated: animated, completion: completion) + toggleChildrenInternal( + cityLocation, + show: true, + showHiddenParents: true, + animated: animated, + completion: completion + ) } } } - private func toggleChildrenInternal(_ relayLocation: RelayLocation, show: Bool, showHiddenParents: Bool, animated: Bool, completion: (() -> Void)? = nil) { + private func toggleChildrenInternal( + _ relayLocation: RelayLocation, + show: Bool, + showHiddenParents: Bool, + animated: Bool, + completion: (() -> Void)? = nil + ) { let affectedRelayLocations: [RelayLocation] if showHiddenParents { affectedRelayLocations = relayLocation.ascendants + [relayLocation] @@ -217,21 +275,21 @@ class LocationDataSource: NSObject, UITableViewDataSource { affectedRelayLocations = [relayLocation] } - let affectedNodes = affectedRelayLocations.compactMap { (relayLocation) -> Node? in + let affectedNodes = affectedRelayLocations.compactMap { relayLocation -> Node? in return nodeByLocation[relayLocation] } // Pick the topmost node to expand or collapse - guard let topNode = affectedNodes.first(where: { (node) -> Bool in + guard let topNode = affectedNodes.first(where: { node -> Bool in return node.isCollapsible && node.showsChildren != show }) else { completion?() return } - let numAffectedChildren = topNode.countChildrenRecursive { (node) -> Bool in + let numAffectedChildren = topNode.countChildrenRecursive { node -> Bool in if show { - return node.showsChildren || affectedNodes.contains(where: { (otherNode) -> Bool in + return node.showsChildren || affectedNodes.contains(where: { otherNode -> Bool in return node === otherNode }) } else { @@ -242,17 +300,20 @@ class LocationDataSource: NSObject, UITableViewDataSource { let applyChanges = { () -> ChangeSet? in guard let topIndexPath = self.indexPath(for: topNode.location) else { return nil } - affectedNodes.forEach { (node) in + affectedNodes.forEach { node in node.showsChildren = show } let affectedRange = (topIndexPath.row + 1 ... topIndexPath.row + numAffectedChildren) - let affectedIndexPaths = affectedRange.map { (row) -> IndexPath in + let affectedIndexPaths = affectedRange.map { row -> IndexPath in return IndexPath(row: row, section: 0) } if show { - self.locationList.insert(contentsOf: topNode.flatRelayLocationList(), at: topIndexPath.row + 1) + self.locationList.insert( + contentsOf: topNode.flatRelayLocationList(), + at: topIndexPath.row + 1 + ) return ChangeSet( insertIndexPaths: affectedIndexPaths, @@ -283,7 +344,7 @@ class LocationDataSource: NSObject, UITableViewDataSource { tableView.deleteRows(at: changeSet.deleteIndexPaths, with: .fade) tableView.reloadRows(at: changeSet.updateIndexPaths, with: .none) } - } completion: { (finished) in + } completion: { finished in restoreSelection() completion?() } @@ -300,20 +361,20 @@ class LocationDataSource: NSObject, UITableViewDataSource { } func item(for indexPath: IndexPath) -> LocationDataSourceItemProtocol? { - return self.relayLocation(for: indexPath) - .flatMap { (relayLocation) -> Node? in + return relayLocation(for: indexPath) + .flatMap { relayLocation -> Node? in return nodeByLocation[relayLocation] } } func indexPath(for location: RelayLocation) -> IndexPath? { - return locationList.firstIndex(of: location).map { (index) -> IndexPath in + return locationList.firstIndex(of: location).map { index -> IndexPath in return IndexPath(row: index, section: 0) } } func indexPathForSelectedRelay() -> IndexPath? { - return selectedRelayLocation.flatMap { (relayLocation) -> IndexPath? in + return selectedRelayLocation.flatMap { relayLocation -> IndexPath? in return self.indexPath(for: relayLocation) } } @@ -331,13 +392,12 @@ class LocationDataSource: NSObject, UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { assert(indexPath.section == 0) - let item = self.item(for: indexPath)! + let item = item(for: indexPath)! return cellProvider(tableView, indexPath, item)! } } extension LocationDataSource { - private enum NodeType { case root case country @@ -373,8 +433,15 @@ extension LocationDataSource { } } - init(type: NodeType, location: RelayLocation, displayName: String, showsChildren: Bool, isActive: Bool, children: [Node]) { - self.nodeType = type + init( + type: NodeType, + location: RelayLocation, + displayName: String, + showsChildren: Bool, + isActive: Bool, + children: [Node] + ) { + nodeType = type self.location = location self.displayName = displayName self.showsChildren = showsChildren @@ -388,7 +455,7 @@ extension LocationDataSource { func sortChildrenRecursive() { sortChildren() - children.forEach { (node) in + children.forEach { node in node.sortChildrenRecursive() } } @@ -401,7 +468,7 @@ extension LocationDataSource { } fallthrough case .city: - isActive = children.contains(where: { (node) -> Bool in + isActive = children.contains(where: { node -> Bool in return node.isActive }) case .relay: @@ -410,7 +477,7 @@ extension LocationDataSource { } func countChildrenRecursive(where condition: @escaping (Node) -> Bool) -> Int { - return children.reduce(into: 0) { (numVisibleChildren, node) in + return children.reduce(into: 0) { numVisibleChildren, node in numVisibleChildren += 1 if condition(node) { numVisibleChildren += node.countChildrenRecursive(where: condition) @@ -419,7 +486,7 @@ extension LocationDataSource { } func flatRelayLocationList() -> [RelayLocation] { - return children.reduce(into: []) { (array, node) in + return children.reduce(into: []) { array, node in Self.flatten(node: node, into: &array) } } @@ -427,12 +494,15 @@ extension LocationDataSource { private func sortChildren() { switch nodeType { case .root, .country: - children.sort { (a, b) -> Bool in + children.sort { a, b -> Bool in return lexicalSortComparator(a.displayName, b.displayName) } case .city: - children.sort { (a, b) -> Bool in - return fileSortComparator(a.location.stringRepresentation, b.location.stringRepresentation) + children.sort { a, b -> Bool in + return fileSortComparator( + a.location.stringRepresentation, + b.location.stringRepresentation + ) } case .relay: break @@ -454,7 +524,6 @@ extension LocationDataSource { let deleteIndexPaths: [IndexPath] let updateIndexPaths: [IndexPath] } - } private func lexicalSortComparator(_ a: String, _ b: String) -> Bool { diff --git a/ios/MullvadVPN/Logging/ChainedError+Logger.swift b/ios/MullvadVPN/Logging/ChainedError+Logger.swift index 2d221c7132..0aede67701 100644 --- a/ios/MullvadVPN/Logging/ChainedError+Logger.swift +++ b/ios/MullvadVPN/Logging/ChainedError+Logger.swift @@ -18,8 +18,7 @@ extension Logger { file: String = #file, function: String = #function, line: UInt = #line - ) - { + ) { log( level: .error, Message( diff --git a/ios/MullvadVPN/Logging/CustomFormatLogHandler.swift b/ios/MullvadVPN/Logging/CustomFormatLogHandler.swift index 340f83d8ae..37b326a2d4 100644 --- a/ios/MullvadVPN/Logging/CustomFormatLogHandler.swift +++ b/ios/MullvadVPN/Logging/CustomFormatLogHandler.swift @@ -38,21 +38,23 @@ struct CustomFormatLogHandler: LogHandler { } } - func log(level: Logger.Level, - message: Logger.Message, - metadata: Logger.Metadata?, - source: String, - file: String, - function: String, - line: UInt) - { - let mergedMetadata = self.metadata.merging(metadata ?? [:]) { (lhs, rhs) -> Logger.MetadataValue in - return rhs - } + func log( + level: Logger.Level, + message: Logger.Message, + metadata: Logger.Metadata?, + source: String, + file: String, + function: String, + line: UInt + ) { + let mergedMetadata = self.metadata + .merging(metadata ?? [:]) { lhs, rhs -> Logger.MetadataValue in + return rhs + } let prettyMetadata = Self.formatMetadata(mergedMetadata) - let metadataOutput = prettyMetadata.isEmpty ? "" : " \(prettyMetadata)" + let metadataOutput = prettyMetadata.isEmpty ? "" : " \(prettyMetadata)" let timestamp = dateFormatter.string(from: Date()) - let formattedMessage = "[\(timestamp)][\(self.label)][\(level)]\(metadataOutput) \(message)\n" + let formattedMessage = "[\(timestamp)][\(label)][\(level)]\(metadataOutput) \(message)\n" for var stream in streams { stream.write(formattedMessage) diff --git a/ios/MullvadVPN/Logging/LogRotation.swift b/ios/MullvadVPN/Logging/LogRotation.swift index 16d48b84df..ff7671ae80 100644 --- a/ios/MullvadVPN/Logging/LogRotation.swift +++ b/ios/MullvadVPN/Logging/LogRotation.swift @@ -9,7 +9,6 @@ import Foundation enum LogRotation { - enum Error: ChainedError { case noSourceLogFile case moveSourceLogFile(Swift.Error) @@ -49,7 +48,8 @@ enum LogRotation { // .fileNoSuchFile is returned when both backup and source log files do not exist // .fileReadNoSuchFile is returned when backup exists but source log file does not if fileError.code == .fileNoSuchFile || fileError.code == .fileReadNoSuchFile, - fileError.url == source { + fileError.url == source + { throw Error.noSourceLogFile } } diff --git a/ios/MullvadVPN/Logging/Logging.swift b/ios/MullvadVPN/Logging/Logging.swift index 55451b677b..8d4f4f883f 100644 --- a/ios/MullvadVPN/Logging/Logging.swift +++ b/ios/MullvadVPN/Logging/Logging.swift @@ -10,21 +10,29 @@ import Foundation import Logging func initLoggingSystem(bundleIdentifier: String, metadata: Logger.Metadata? = nil) { - let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: ApplicationConfiguration.securityGroupIdentifier)! + let containerURL = FileManager.default + .containerURL( + forSecurityApplicationGroupIdentifier: ApplicationConfiguration + .securityGroupIdentifier + )! let logsDirectoryURL = containerURL.appendingPathComponent("Logs", isDirectory: true) let logFileName = "\(bundleIdentifier).log" let logFileURL = logsDirectoryURL.appendingPathComponent(logFileName) // Create Logs folder within container if it doesn't exist - try? FileManager.default.createDirectory(at: logsDirectoryURL, withIntermediateDirectories: false, attributes: nil) + try? FileManager.default.createDirectory( + at: logsDirectoryURL, + withIntermediateDirectories: false, + attributes: nil + ) // Rotate log var logRotationError: Error? do { try LogRotation.rotateLog( - logsDirectory: logsDirectoryURL, - logFileName: logFileName - ) + logsDirectory: logsDirectoryURL, + logFileName: logFileName + ) } catch { logRotationError = error } @@ -38,11 +46,11 @@ func initLoggingSystem(bundleIdentifier: String, metadata: Logger.Metadata? = ni } // Configure Logging system - LoggingSystem.bootstrap { (label) -> LogHandler in + LoggingSystem.bootstrap { label -> LogHandler in var logHandlers: [LogHandler] = [] #if DEBUG - logHandlers.append(OSLogHandler(subsystem: bundleIdentifier, category: label)) + logHandlers.append(OSLogHandler(subsystem: bundleIdentifier, category: label)) #endif if !streams.isEmpty { diff --git a/ios/MullvadVPN/Logging/OSLogHandler.swift b/ios/MullvadVPN/Logging/OSLogHandler.swift index 8fcddcea15..0b71bd08e5 100644 --- a/ios/MullvadVPN/Logging/OSLogHandler.swift +++ b/ios/MullvadVPN/Logging/OSLogHandler.swift @@ -40,8 +40,8 @@ struct OSLogHandler: LogHandler { } init(subsystem: String, category: String) { - self.label = category - self.osLog = OSLogHandler.getOSLog(subsystem: subsystem, category: category) + label = category + osLog = OSLogHandler.getOSLog(subsystem: subsystem, category: category) } subscript(metadataKey metadataKey: String) -> Logging.Logger.Metadata.Value? { @@ -53,17 +53,19 @@ struct OSLogHandler: LogHandler { } } - func log(level: Logging.Logger.Level, - message: Logging.Logger.Message, - metadata: Logging.Logger.Metadata?, - source: String, - file: String, - function: String, - line: UInt) - { - let mergedMetadata = self.metadata.merging(metadata ?? [:]) { (lhs, rhs) -> Logging.Logger.MetadataValue in - return rhs - } + func log( + level: Logging.Logger.Level, + message: Logging.Logger.Message, + metadata: Logging.Logger.Metadata?, + source: String, + file: String, + function: String, + line: UInt + ) { + let mergedMetadata = self.metadata + .merging(metadata ?? [:]) { lhs, rhs -> Logging.Logger.MetadataValue in + return rhs + } let prettyMetadata = Self.formatMetadata(mergedMetadata) let logMessage = prettyMetadata.isEmpty ? message : "\(prettyMetadata) \(message)" @@ -74,6 +76,7 @@ struct OSLogHandler: LogHandler { return metadata.map { "\($0)=\($1)" }.joined(separator: " ") } } + extension Logging.Logger.Level { var osLogType: OSLogType { switch self { diff --git a/ios/MullvadVPN/Logging/TextFileOutputStream.swift b/ios/MullvadVPN/Logging/TextFileOutputStream.swift index 751f897b7d..95387b53ec 100644 --- a/ios/MullvadVPN/Logging/TextFileOutputStream.swift +++ b/ios/MullvadVPN/Logging/TextFileOutputStream.swift @@ -14,19 +14,27 @@ class TextFileOutputStream: TextOutputStream { private let queue = DispatchQueue.global(qos: .utility) class func standardOutputStream(encoding: String.Encoding = .utf8) -> TextFileOutputStream { - return TextFileOutputStream(fileDescriptor: FileHandle.standardOutput.fileDescriptor, encoding: encoding) + return TextFileOutputStream( + fileDescriptor: FileHandle.standardOutput.fileDescriptor, + encoding: encoding + ) } init(fileDescriptor: Int32, encoding: String.Encoding = .utf8) { self.encoding = encoding - writer = DispatchIO(type: .stream, fileDescriptor: fileDescriptor, queue: queue) { (errno) in + writer = DispatchIO(type: .stream, fileDescriptor: fileDescriptor, queue: queue) { errno in if errno != 0 { print("TextFileOutputStream: closed channel with error: \(errno)") } } } - init?(fileURL: URL, createFile: Bool, filePermissions: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, encoding: String.Encoding = .utf8) { + init?( + fileURL: URL, + createFile: Bool, + filePermissions: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, + encoding: String.Encoding = .utf8 + ) { var oflag: Int32 = O_WRONLY var mode: mode_t = .zero if createFile { @@ -34,13 +42,20 @@ class TextFileOutputStream: TextOutputStream { mode = filePermissions } - let queue = self.queue - let writer = fileURL.path.withCString { (filePathPointer) -> DispatchIO? in - return DispatchIO(type: .stream, path: filePathPointer, oflag: oflag, mode: mode, queue: queue, cleanupHandler: { (errno) in - if errno != 0 { - print("TextFileOutputStream: closed channel with error: \(errno)") + let queue = queue + let writer = fileURL.path.withCString { filePathPointer -> DispatchIO? in + return DispatchIO( + type: .stream, + path: filePathPointer, + oflag: oflag, + mode: mode, + queue: queue, + cleanupHandler: { errno in + if errno != 0 { + print("TextFileOutputStream: closed channel with error: \(errno)") + } } - }) + ) } if let writer = writer { @@ -56,12 +71,17 @@ class TextFileOutputStream: TextOutputStream { } func write(_ string: String) { - string.data(using: encoding)?.withUnsafeBytes { (bytes) in - writer.write(offset: .zero, data: DispatchData(bytes: bytes), queue: queue) { (done, data, errno) in - if errno != 0 { - print("TextFileOutputStream: write error: \(errno)") + string.data(using: encoding)?.withUnsafeBytes { bytes in + writer + .write( + offset: .zero, + data: DispatchData(bytes: bytes), + queue: queue + ) { done, data, errno in + if errno != 0 { + print("TextFileOutputStream: write error: \(errno)") + } } - } } } } diff --git a/ios/MullvadVPN/LoginContentView.swift b/ios/MullvadVPN/LoginContentView.swift index 661ab8401e..a89d6c80ef 100644 --- a/ios/MullvadVPN/LoginContentView.swift +++ b/ios/MullvadVPN/LoginContentView.swift @@ -9,7 +9,6 @@ import UIKit class LoginContentView: UIView { - private var keyboardResponder: AutomaticKeyboardResponder? let titleLabel: UILabel = { @@ -114,12 +113,15 @@ class LoginContentView: UIView { accountInputGroup.textField.accessibilityIdentifier = "LoginTextField" - keyboardResponder = AutomaticKeyboardResponder(targetView: self, handler: { [weak self] (view, adjustment) in - self?.contentContainerBottomConstraint?.constant = adjustment + keyboardResponder = AutomaticKeyboardResponder( + targetView: self, + handler: { [weak self] view, adjustment in + self?.contentContainerBottomConstraint?.constant = adjustment - self?.layoutIfNeeded() - self?.updateStatusImageVisibility(animated: false) - }) + self?.layoutIfNeeded() + self?.updateStatusImageVisibility(animated: false) + } + ) addSubviews() } @@ -139,7 +141,8 @@ class LoginContentView: UIView { private func updateStatusImageVisibility(animated: Bool) { let statusImageFrame = statusImageView.convert(statusImageView.bounds, to: self) - let shouldShow = isStatusImageVisible && safeAreaLayoutGuide.layoutFrame.contains(statusImageFrame) + let shouldShow = isStatusImageVisible && safeAreaLayoutGuide.layoutFrame + .contains(statusImageFrame) let actions = { // Only display the status image if it doesn't overlap the safe area layout guide. @@ -175,7 +178,8 @@ class LoginContentView: UIView { addSubview(contentContainer) addSubview(footerContainer) - let contentContainerBottomConstraint = bottomAnchor.constraint(equalTo: contentContainer.bottomAnchor) + let contentContainerBottomConstraint = bottomAnchor + .constraint(equalTo: contentContainer.bottomAnchor) self.contentContainerBottomConstraint = contentContainerBottomConstraint NSLayoutConstraint.activate([ @@ -189,17 +193,28 @@ class LoginContentView: UIView { footerContainer.bottomAnchor.constraint(equalTo: bottomAnchor), footerLabel.topAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.topAnchor), - footerLabel.leadingAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.leadingAnchor), - footerLabel.trailingAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.trailingAnchor), + footerLabel.leadingAnchor + .constraint(equalTo: footerContainer.layoutMarginsGuide.leadingAnchor), + footerLabel.trailingAnchor + .constraint(equalTo: footerContainer.layoutMarginsGuide.trailingAnchor), - createAccountButton.topAnchor.constraint(equalToSystemSpacingBelow: footerLabel.bottomAnchor, multiplier: 1), - createAccountButton.leadingAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.leadingAnchor), - createAccountButton.trailingAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.trailingAnchor), - createAccountButton.bottomAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.bottomAnchor), + createAccountButton.topAnchor.constraint( + equalToSystemSpacingBelow: footerLabel.bottomAnchor, + multiplier: 1 + ), + createAccountButton.leadingAnchor + .constraint(equalTo: footerContainer.layoutMarginsGuide.leadingAnchor), + createAccountButton.trailingAnchor + .constraint(equalTo: footerContainer.layoutMarginsGuide.trailingAnchor), + createAccountButton.bottomAnchor + .constraint(equalTo: footerContainer.layoutMarginsGuide.bottomAnchor), statusImageView.centerXAnchor.constraint(equalTo: contentContainer.centerXAnchor), formContainer.topAnchor.constraint(equalTo: statusImageView.bottomAnchor, constant: 30), - formContainer.centerYAnchor.constraint(equalTo: contentContainer.centerYAnchor, constant: -20), + formContainer.centerYAnchor.constraint( + equalTo: contentContainer.centerYAnchor, + constant: -20 + ), formContainer.leadingAnchor.constraint(equalTo: contentContainer.leadingAnchor), formContainer.trailingAnchor.constraint(equalTo: contentContainer.trailingAnchor), formContainer.bottomAnchor.constraint(equalTo: accountInputGroupWrapper.bottomAnchor), @@ -208,22 +223,36 @@ class LoginContentView: UIView { activityIndicator.centerYAnchor.constraint(equalTo: statusImageView.centerYAnchor), titleLabel.topAnchor.constraint(equalTo: formContainer.topAnchor), - titleLabel.leadingAnchor.constraint(equalTo: formContainer.layoutMarginsGuide.leadingAnchor), - titleLabel.trailingAnchor.constraint(equalTo: formContainer.layoutMarginsGuide.trailingAnchor), + titleLabel.leadingAnchor + .constraint(equalTo: formContainer.layoutMarginsGuide.leadingAnchor), + titleLabel.trailingAnchor + .constraint(equalTo: formContainer.layoutMarginsGuide.trailingAnchor), - messageLabel.topAnchor.constraint(equalToSystemSpacingBelow: titleLabel.bottomAnchor, multiplier: 1), - messageLabel.leadingAnchor.constraint(equalTo: formContainer.layoutMarginsGuide.leadingAnchor), - messageLabel.trailingAnchor.constraint(equalTo: formContainer.layoutMarginsGuide.trailingAnchor), + messageLabel.topAnchor.constraint( + equalToSystemSpacingBelow: titleLabel.bottomAnchor, + multiplier: 1 + ), + messageLabel.leadingAnchor + .constraint(equalTo: formContainer.layoutMarginsGuide.leadingAnchor), + messageLabel.trailingAnchor + .constraint(equalTo: formContainer.layoutMarginsGuide.trailingAnchor), - accountInputGroupWrapper.topAnchor.constraint(equalToSystemSpacingBelow: messageLabel.bottomAnchor, multiplier: 1), - accountInputGroupWrapper.leadingAnchor.constraint(equalTo: formContainer.layoutMarginsGuide.leadingAnchor), - accountInputGroupWrapper.trailingAnchor.constraint(equalTo: formContainer.layoutMarginsGuide.trailingAnchor), - accountInputGroupWrapper.heightAnchor.constraint(equalTo: accountInputGroup.contentView.heightAnchor), + accountInputGroupWrapper.topAnchor.constraint( + equalToSystemSpacingBelow: messageLabel.bottomAnchor, + multiplier: 1 + ), + accountInputGroupWrapper.leadingAnchor + .constraint(equalTo: formContainer.layoutMarginsGuide.leadingAnchor), + accountInputGroupWrapper.trailingAnchor + .constraint(equalTo: formContainer.layoutMarginsGuide.trailingAnchor), + accountInputGroupWrapper.heightAnchor + .constraint(equalTo: accountInputGroup.contentView.heightAnchor), accountInputGroup.topAnchor.constraint(equalTo: accountInputGroupWrapper.topAnchor), - accountInputGroup.leadingAnchor.constraint(equalTo: accountInputGroupWrapper.leadingAnchor), - accountInputGroup.trailingAnchor.constraint(equalTo: accountInputGroupWrapper.trailingAnchor), + accountInputGroup.leadingAnchor + .constraint(equalTo: accountInputGroupWrapper.leadingAnchor), + accountInputGroup.trailingAnchor + .constraint(equalTo: accountInputGroupWrapper.trailingAnchor), ]) } - } diff --git a/ios/MullvadVPN/LoginViewController.swift b/ios/MullvadVPN/LoginViewController.swift index b6e9a3df71..20890e4ecf 100644 --- a/ios/MullvadVPN/LoginViewController.swift +++ b/ios/MullvadVPN/LoginViewController.swift @@ -6,8 +6,8 @@ // Copyright © 2019 Mullvad VPN AB. All rights reserved. // -import UIKit import Logging +import UIKit enum LoginAction { case useExistingAccount(String) @@ -15,7 +15,7 @@ enum LoginAction { var setAccountAction: SetAccountAction { switch self { - case .useExistingAccount(let accountNumber): + case let .useExistingAccount(accountNumber): return .existing(accountNumber) case .createAccount: return .new @@ -41,16 +41,17 @@ protocol LoginViewControllerDelegate: AnyObject { } class LoginViewController: UIViewController, RootContainment { - private lazy var contentView: LoginContentView = { let view = LoginContentView(frame: self.view.bounds) view.translatesAutoresizingMaskIntoConstraints = false return view }() - private lazy var accountInputAccessoryCancelButton: UIBarButtonItem = { - return UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelLogin)) - }() + private lazy var accountInputAccessoryCancelButton = UIBarButtonItem( + barButtonSystemItem: .cancel, + target: self, + action: #selector(cancelLogin) + ) private lazy var accountInputAccessoryLoginButton: UIBarButtonItem = { let barButtonItem = UIBarButtonItem( @@ -74,7 +75,7 @@ class LoginViewController: UIViewController, RootContainment { toolbar.items = [ self.accountInputAccessoryCancelButton, UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil), - self.accountInputAccessoryLoginButton + self.accountInputAccessoryLoginButton, ] toolbar.sizeToFit() return toolbar @@ -129,7 +130,8 @@ class LoginViewController: UIViewController, RootContainment { // There is no need to set the input accessory toolbar on iPad since it has a dedicated // button to dismiss the keyboard. if case .phone = UIDevice.current.userInterfaceIdiom { - contentView.accountInputGroup.textField.inputAccessoryView = self.accountInputAccessoryToolbar + contentView.accountInputGroup.textField.inputAccessoryView = self + .accountInputAccessoryToolbar } else { contentView.accountInputGroup.textField.inputAccessoryView = nil } @@ -140,12 +142,18 @@ class LoginViewController: UIViewController, RootContainment { let notificationCenter = NotificationCenter.default - contentView.createAccountButton.addTarget(self, action: #selector(createNewAccount), for: .touchUpInside) + contentView.createAccountButton.addTarget( + self, + action: #selector(createNewAccount), + for: .touchUpInside + ) - notificationCenter.addObserver(self, - selector: #selector(textDidChange(_:)), - name: UITextField.textDidChangeNotification, - object: contentView.accountInputGroup.textField) + notificationCenter.addObserver( + self, + selector: #selector(textDidChange(_:)), + name: UITextField.textDidChangeNotification, + object: contentView.accountInputGroup.textField + ) } override var disablesAutomaticKeyboardDismissal: Bool { @@ -164,20 +172,21 @@ class LoginViewController: UIViewController, RootContainment { func start(action: LoginAction) { beginLogin(action) - delegate?.loginViewController(self, shouldHandleLoginAction: action) { [weak self] completion in - switch completion { - case .success(let accountData): - if case .createAccount = action { - self?.contentView.accountInputGroup.setAccount(accountData?.number ?? "") - } + delegate? + .loginViewController(self, shouldHandleLoginAction: action) { [weak self] completion in + switch completion { + case let .success(accountData): + if case .createAccount = action { + self?.contentView.accountInputGroup.setAccount(accountData?.number ?? "") + } - self?.endLogin(.success(action)) - case .failure(let error): - self?.endLogin(.failure(error)) - case .cancelled: - self?.endLogin(.default) + self?.endLogin(.success(action)) + case let .failure(error): + self?.endLogin(.failure(error)) + case .cancelled: + self?.endLogin(.default) + } } - } } func reset() { @@ -226,8 +235,10 @@ class LoginViewController: UIViewController, RootContainment { contentView.accountInputGroup.setLastUsedAccount(accountNumber, animated: false) } catch { - logger.error(chainedError: AnyChainedError(error), - message: "Failed to update last used account.") + logger.error( + chainedError: AnyChainedError(error), + message: "Failed to update last used account." + ) } } @@ -298,7 +309,7 @@ class LoginViewController: UIViewController, RootContainment { // Disable "Create account" button on iPad as user types in the account token, // however leave it enabled on iPhone to avoid confusion to why it's being disabled // since it's likely overlayed by keyboard. - if case .pad = self.traitCollection.userInterfaceIdiom { + if case .pad = traitCollection.userInterfaceIdiom { isEnabled = contentView.accountInputGroup.textField.text?.isEmpty ?? true } else { isEnabled = true @@ -369,7 +380,7 @@ private extension LoginState { comment: "" ) - case .authenticating(let method): + case let .authenticating(method): switch method { case .useExistingAccount: return NSLocalizedString( @@ -387,10 +398,10 @@ private extension LoginState { ) } - case .failure(let error): + case let .failure(error): return error.localizedDescription - case .success(let method): + case let .success(method): switch method { case .useExistingAccount: return NSLocalizedString( @@ -419,8 +430,10 @@ extension LoginViewController: AccountInputGroupViewDelegate { try SettingsManager.setLastUsedAccount(nil) return true } catch { - logger.error(chainedError: AnyChainedError(error), - message: "Failed to remove last used account.") + logger.error( + chainedError: AnyChainedError(error), + message: "Failed to remove last used account." + ) return false } } diff --git a/ios/MullvadVPN/NEProviderStopReason+Debug.swift b/ios/MullvadVPN/NEProviderStopReason+Debug.swift index eab9028dbe..35d5339c58 100644 --- a/ios/MullvadVPN/NEProviderStopReason+Debug.swift +++ b/ios/MullvadVPN/NEProviderStopReason+Debug.swift @@ -13,42 +13,41 @@ extension NEProviderStopReason: CustomStringConvertible { public var description: String { switch self { case .none: - return "none" + return "none" case .userInitiated: - return "user initiated" + return "user initiated" case .providerFailed: - return "provider failed" + return "provider failed" case .noNetworkAvailable: - return "no network available" + return "no network available" case .unrecoverableNetworkChange: - return "unrecoverable network change" + return "unrecoverable network change" case .providerDisabled: - return "provider disabled" + return "provider disabled" case .authenticationCanceled: - return "authentication cancelled" + return "authentication cancelled" case .configurationFailed: - return "configuration failed" + return "configuration failed" case .idleTimeout: - return "idle timeout" + return "idle timeout" case .configurationDisabled: - return "configuration disabled" + return "configuration disabled" case .configurationRemoved: - return "configuration removed" + return "configuration removed" case .superceded: - return "superceded" + return "superceded" case .userLogout: - return "user logout" + return "user logout" case .userSwitch: - return "user switch" + return "user switch" case .connectionFailed: - return "connection failed" + return "connection failed" case .sleep: - return "sleep" + return "sleep" case .appUpdate: - return "app update" + return "app update" @unknown default: - return "unknown value (\(self.rawValue))" + return "unknown value (\(rawValue))" } } } - diff --git a/ios/MullvadVPN/NEVPNStatus+Debug.swift b/ios/MullvadVPN/NEVPNStatus+Debug.swift index ba612d8b1a..37520fb3d5 100644 --- a/ios/MullvadVPN/NEVPNStatus+Debug.swift +++ b/ios/MullvadVPN/NEVPNStatus+Debug.swift @@ -21,11 +21,11 @@ extension NEVPNStatus: CustomStringConvertible { case .disconnecting: return "disconnecting" case .invalid: - return "invalid" + return "invalid" case .reasserting: return "reasserting" @unknown default: - return "unknown value (\(self.rawValue))" + return "unknown value (\(rawValue))" } } } diff --git a/ios/MullvadVPN/NSAttributedString+Markdown.swift b/ios/MullvadVPN/NSAttributedString+Markdown.swift index af13fb5b95..5e7c064b9d 100644 --- a/ios/MullvadVPN/NSAttributedString+Markdown.swift +++ b/ios/MullvadVPN/NSAttributedString+Markdown.swift @@ -13,7 +13,8 @@ extension NSAttributedString { let attributedString = NSMutableAttributedString() let components = markdownString.components(separatedBy: "**") - let fontDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold) ?? font.fontDescriptor + let fontDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold) ?? font + .fontDescriptor let boldFont = UIFont(descriptor: fontDescriptor, size: font.pointSize) for (index, string) in components.enumerated() { diff --git a/ios/MullvadVPN/NotificationBannerView.swift b/ios/MullvadVPN/NotificationBannerView.swift index 5c46fabd74..bd74ee3dbf 100644 --- a/ios/MullvadVPN/NotificationBannerView.swift +++ b/ios/MullvadVPN/NotificationBannerView.swift @@ -24,7 +24,6 @@ enum NotificationBannerStyle { } class NotificationBannerView: UIView { - private static let indicatorViewSize = CGSize(width: 12, height: 12) private let backgroundView: UIVisualEffectView = { @@ -116,22 +115,31 @@ class NotificationBannerView: UIView { wrapperView.topAnchor.constraint(equalTo: backgroundView.contentView.topAnchor), wrapperView.leadingAnchor.constraint(equalTo: backgroundView.contentView.leadingAnchor), - wrapperView.trailingAnchor.constraint(equalTo: backgroundView.contentView.trailingAnchor), + wrapperView.trailingAnchor + .constraint(equalTo: backgroundView.contentView.trailingAnchor), wrapperView.bottomAnchor.constraint(equalTo: backgroundView.contentView.bottomAnchor), indicatorView.bottomAnchor.constraint(equalTo: titleLabel.firstBaselineAnchor), - indicatorView.leadingAnchor.constraint(equalTo: wrapperView.layoutMarginsGuide.leadingAnchor), + indicatorView.leadingAnchor + .constraint(equalTo: wrapperView.layoutMarginsGuide.leadingAnchor), indicatorView.widthAnchor.constraint(equalToConstant: Self.indicatorViewSize.width), indicatorView.heightAnchor.constraint(equalToConstant: Self.indicatorViewSize.height), titleLabel.topAnchor.constraint(equalTo: wrapperView.layoutMarginsGuide.topAnchor), - titleLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: indicatorView.trailingAnchor, multiplier: 1), - titleLabel.trailingAnchor.constraint(equalTo: wrapperView.layoutMarginsGuide.trailingAnchor), + titleLabel.leadingAnchor.constraint( + equalToSystemSpacingAfter: indicatorView.trailingAnchor, + multiplier: 1 + ), + titleLabel.trailingAnchor + .constraint(equalTo: wrapperView.layoutMarginsGuide.trailingAnchor), - bodyLabel.topAnchor.constraint(equalToSystemSpacingBelow: titleLabel.bottomAnchor, multiplier: 1), + bodyLabel.topAnchor.constraint( + equalToSystemSpacingBelow: titleLabel.bottomAnchor, + multiplier: 1 + ), bodyLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), bodyLabel.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor), - bodyLabel.bottomAnchor.constraint(equalTo: wrapperView.layoutMarginsGuide.bottomAnchor) + bodyLabel.bottomAnchor.constraint(equalTo: wrapperView.layoutMarginsGuide.bottomAnchor), ]) } diff --git a/ios/MullvadVPN/NotificationController.swift b/ios/MullvadVPN/NotificationController.swift index ed7d03e0ee..9585c26ff9 100644 --- a/ios/MullvadVPN/NotificationController.swift +++ b/ios/MullvadVPN/NotificationController.swift @@ -85,7 +85,10 @@ class NotificationController: UIViewController { } if animated { - let timing = UISpringTimingParameters(dampingRatio: 0.7, initialVelocity: CGVector(dx: 0, dy: 1)) + let timing = UISpringTimingParameters( + dampingRatio: 0.7, + initialVelocity: CGVector(dx: 0, dy: 1) + ) let animator = UIViewPropertyAnimator(duration: 0.8, timingParameters: timing) animator.isInterruptible = false animator.addAnimations { @@ -112,7 +115,10 @@ class NotificationController: UIViewController { bannerView.accessibilityLabel = "\(notification.title)\n\(notification.body)" if animated { - let animator = UIViewPropertyAnimator(duration: 0.25, timingParameters: UICubicTimingParameters(animationCurve: .easeOut)) + let animator = UIViewPropertyAnimator( + duration: 0.25, + timingParameters: UICubicTimingParameters(animationCurve: .easeOut) + ) animator.addAnimations { self.view.layoutIfNeeded() } @@ -138,7 +144,9 @@ class NotificationController: UIViewController { private func updateAccessibilityFrame() { let layoutFrame = bannerView.layoutMarginsGuide.layoutFrame - bannerView.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(layoutFrame, in: view) + bannerView.accessibilityFrame = UIAccessibility.convertToScreenCoordinates( + layoutFrame, + in: view + ) } - } diff --git a/ios/MullvadVPN/NotificationManager.swift b/ios/MullvadVPN/NotificationManager.swift index d2b1842cd6..63468e70a3 100644 --- a/ios/MullvadVPN/NotificationManager.swift +++ b/ios/MullvadVPN/NotificationManager.swift @@ -7,15 +7,18 @@ // import Foundation -import UserNotifications import Logging import UIKit +import UserNotifications protocol NotificationManagerDelegate: AnyObject { - func notificationManagerDidUpdateInAppNotifications(_ manager: NotificationManager, notifications: [InAppNotificationDescriptor]) + func notificationManagerDidUpdateInAppNotifications( + _ manager: NotificationManager, + notifications: [InAppNotificationDescriptor] + ) } -fileprivate protocol NotificationProviderDelegate: AnyObject { +private protocol NotificationProviderDelegate: AnyObject { func notificationProviderDidInvalidate(_ notificationProvider: NotificationProvider) } @@ -27,8 +30,9 @@ class NotificationProvider { } func invalidate() { - let executor = { () -> Void in + let executor = { self.delegate?.notificationProviderDidInvalidate(self) + return } if Thread.isMainThread { @@ -62,7 +66,6 @@ protocol InAppNotificationProvider { } class NotificationManager: NotificationProviderDelegate { - private lazy var logger = Logger(label: "NotificationManager") var notificationProviders: [NotificationProvider] = [] { @@ -87,7 +90,10 @@ class NotificationManager: NotificationProviderDelegate { didSet { // Pump in-app notifications when changing delegate. if !inAppNotificationDescriptors.isEmpty { - delegate?.notificationManagerDidUpdateInAppNotifications(self, notifications: inAppNotificationDescriptors) + delegate?.notificationManagerDidUpdateInAppNotifications( + self, + notifications: inAppNotificationDescriptors + ) } } } @@ -127,14 +133,16 @@ class NotificationManager: NotificationProviderDelegate { } let notificationCenter = UNUserNotificationCenter.current() - notificationCenter.removePendingNotificationRequests(withIdentifiers: pendingRequestIdentifiersToRemove) - notificationCenter.removeDeliveredNotifications(withIdentifiers: deliveredRequestIdentifiersToRemove) + notificationCenter + .removePendingNotificationRequests(withIdentifiers: pendingRequestIdentifiersToRemove) + notificationCenter + .removeDeliveredNotifications(withIdentifiers: deliveredRequestIdentifiersToRemove) - requestNotificationPermissions { (granted) in + requestNotificationPermissions { granted in guard granted else { return } for newRequest in newSystemNotificationRequests { - notificationCenter.add(newRequest) { (error) in + notificationCenter.add(newRequest) { error in if let error = error { self.logger.error( chainedError: AnyChainedError(error), @@ -147,7 +155,10 @@ class NotificationManager: NotificationProviderDelegate { inAppNotificationDescriptors = newInAppNotificationDescriptors - delegate?.notificationManagerDidUpdateInAppNotifications(self, notifications: newInAppNotificationDescriptors) + delegate?.notificationManagerDidUpdateInAppNotifications( + self, + notifications: newInAppNotificationDescriptors + ) } // MARK: - Private @@ -156,18 +167,19 @@ class NotificationManager: NotificationProviderDelegate { let authorizationOptions: UNAuthorizationOptions = [.alert, .sound, .provisional] let userNotificationCenter = UNUserNotificationCenter.current() - userNotificationCenter.getNotificationSettings { (notificationSettings) in + userNotificationCenter.getNotificationSettings { notificationSettings in switch notificationSettings.authorizationStatus { case .notDetermined: - userNotificationCenter.requestAuthorization(options: authorizationOptions) { (granted, error) in - if let error = error { - self.logger.error( - chainedError: AnyChainedError(error), - message: "Failed to obtain user notifications authorization" - ) + userNotificationCenter + .requestAuthorization(options: authorizationOptions) { granted, error in + if let error = error { + self.logger.error( + chainedError: AnyChainedError(error), + message: "Failed to obtain user notifications authorization" + ) + } + completion(granted) } - completion(granted) - } case .authorized, .provisional: completion(true) @@ -191,20 +203,31 @@ class NotificationManager: NotificationProviderDelegate { let notificationCenter = UNUserNotificationCenter.current() if notificationProvider.shouldRemovePendingRequests { - notificationCenter.removePendingNotificationRequests(withIdentifiers: [notificationProvider.identifier]) + notificationCenter + .removePendingNotificationRequests(withIdentifiers: [ + notificationProvider + .identifier, + ]) } if notificationProvider.shouldRemoveDeliveredRequests { - notificationCenter.removeDeliveredNotifications(withIdentifiers: [notificationProvider.identifier]) + notificationCenter + .removeDeliveredNotifications(withIdentifiers: [ + notificationProvider + .identifier, + ]) } if let request = notificationProvider.notificationRequest { - requestNotificationPermissions { (granted) in + requestNotificationPermissions { granted in guard granted else { return } - notificationCenter.add(request) { (error) in + notificationCenter.add(request) { error in if let error = error { - self.logger.error("Failed to add notification request with identifier \(request.identifier). Error: \(error.localizedDescription)") + self.logger + .error( + "Failed to add notification request with identifier \(request.identifier). Error: \(error.localizedDescription)" + ) } } } @@ -216,24 +239,30 @@ class NotificationManager: NotificationProviderDelegate { var newNotificationDescriptors = inAppNotificationDescriptors if let replaceNotificationDescriptor = notificationProvider.notificationDescriptor { - newNotificationDescriptors = notificationProviders.compactMap { (notificationProvider) -> InAppNotificationDescriptor? in - if replaceNotificationDescriptor.identifier == notificationProvider.identifier { - return replaceNotificationDescriptor - } else { - return inAppNotificationDescriptors.first { (descriptor) in - return descriptor.identifier == notificationProvider.identifier + newNotificationDescriptors = notificationProviders + .compactMap { notificationProvider -> InAppNotificationDescriptor? in + if replaceNotificationDescriptor.identifier == notificationProvider + .identifier + { + return replaceNotificationDescriptor + } else { + return inAppNotificationDescriptors.first { descriptor in + return descriptor.identifier == notificationProvider.identifier + } } } - } } else { - newNotificationDescriptors.removeAll { (descriptor) in + newNotificationDescriptors.removeAll { descriptor in return descriptor.identifier == notificationProvider.identifier } } inAppNotificationDescriptors = newNotificationDescriptors - delegate?.notificationManagerDidUpdateInAppNotifications(self, notifications: inAppNotificationDescriptors) + delegate?.notificationManagerDidUpdateInAppNotifications( + self, + notifications: inAppNotificationDescriptors + ) } } } diff --git a/ios/MullvadVPN/Notifications/AccountExpiryNotificationProvider.swift b/ios/MullvadVPN/Notifications/AccountExpiryNotificationProvider.swift index ae3942ca30..1ace9cc5bb 100644 --- a/ios/MullvadVPN/Notifications/AccountExpiryNotificationProvider.swift +++ b/ios/MullvadVPN/Notifications/AccountExpiryNotificationProvider.swift @@ -12,7 +12,9 @@ import UserNotifications let accountExpiryNotificationIdentifier = "net.mullvad.MullvadVPN.AccountExpiryNotification" let accountExpiryDefaultTriggerInterval = 3 -class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificationProvider, InAppNotificationProvider, TunnelObserver { +class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificationProvider, + InAppNotificationProvider, TunnelObserver +{ private var accountExpiry: Date? /// Interval prior to expiry used to calculate when to trigger notifications. @@ -35,13 +37,20 @@ class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificatio guard let accountExpiry = accountExpiry else { return nil } // Subtract 3 days from expiry date - guard let triggerDate = Calendar.current.date(byAdding: .day, value: -triggerInterval, to: accountExpiry) else { return nil } + guard let triggerDate = Calendar.current.date( + byAdding: .day, + value: -triggerInterval, + to: accountExpiry + ) else { return nil } // Do not produce notification if less than 3 days left till expiry guard triggerDate > Date() else { return nil } // Create date components for calendar trigger - let dateComponents = Calendar.current.dateComponents([.second, .minute, .hour, .day, .month, .year], from: triggerDate) + let dateComponents = Calendar.current.dateComponents( + [.second, .minute, .hour, .day, .month, .year], + from: triggerDate + ) return UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false) } @@ -65,8 +74,14 @@ class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificatio ) let content = UNMutableNotificationContent() - content.title = NSString.localizedUserNotificationString(forKey: "ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_TITLE", arguments: nil) - content.body = NSString.localizedUserNotificationString(forKey: "ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_BODY", arguments: nil) + content.title = NSString.localizedUserNotificationString( + forKey: "ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_TITLE", + arguments: nil + ) + content.body = NSString.localizedUserNotificationString( + forKey: "ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_BODY", + arguments: nil + ) content.sound = UNNotificationSound.default return UNNotificationRequest( @@ -92,11 +107,15 @@ class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificatio guard let accountExpiry = accountExpiry else { return nil } // Subtract 3 days from expiry date - guard let triggerDate = Calendar.current.date(byAdding: .day, value: -triggerInterval, to: accountExpiry) else { return nil } + guard let triggerDate = Calendar.current.date( + byAdding: .day, + value: -triggerInterval, + to: accountExpiry + ) else { return nil } // Only produce in-app notification within the last 3 days till expiry let now = Date() - guard triggerDate < now && now < accountExpiry else { return nil } + guard triggerDate < now, now < accountExpiry else { return nil } // Format the remaining duration let formatter = DateComponentsFormatter() @@ -107,7 +126,7 @@ class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificatio guard let duration = formatter.string(from: now, to: accountExpiry) else { return nil } return InAppNotificationDescriptor( - identifier: self.identifier, + identifier: identifier, style: .warning, title: NSLocalizedString( "ACCOUNT_EXPIRY_INAPP_NOTIFICATION_TITLE", @@ -143,12 +162,14 @@ class AccountExpiryNotificationProvider: NotificationProvider, SystemNotificatio // no-op } - func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2) { + func tunnelManager( + _ manager: TunnelManager, + didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2 + ) { // no-op } func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState) { invalidate(deviceState: manager.deviceState) } - } diff --git a/ios/MullvadVPN/Notifications/TunnelErrorNotificationProvider.swift b/ios/MullvadVPN/Notifications/TunnelErrorNotificationProvider.swift index 24c21479f2..6c50218e22 100644 --- a/ios/MullvadVPN/Notifications/TunnelErrorNotificationProvider.swift +++ b/ios/MullvadVPN/Notifications/TunnelErrorNotificationProvider.swift @@ -8,7 +8,9 @@ import Foundation -class TunnelErrorNotificationProvider: NotificationProvider, InAppNotificationProvider, TunnelObserver { +class TunnelErrorNotificationProvider: NotificationProvider, InAppNotificationProvider, + TunnelObserver +{ override var identifier: String { return "net.mullvad.MullvadVPN.TunnelErrorNotificationProvider" } @@ -55,7 +57,10 @@ class TunnelErrorNotificationProvider: NotificationProvider, InAppNotificationPr invalidate() } - func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2) { + func tunnelManager( + _ manager: TunnelManager, + didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2 + ) { // no-op } diff --git a/ios/MullvadVPN/ObserverList.swift b/ios/MullvadVPN/ObserverList.swift index b936ae3bdf..a2093c9809 100644 --- a/ios/MullvadVPN/ObserverList.swift +++ b/ios/MullvadVPN/ObserverList.swift @@ -13,7 +13,7 @@ struct WeakBox<T> { return valueProvider() } - private let valueProvider: (() -> T?) + private let valueProvider: () -> T? init(_ value: T) { let reference = value as AnyObject diff --git a/ios/MullvadVPN/Operations/AsyncBlockOperation.swift b/ios/MullvadVPN/Operations/AsyncBlockOperation.swift index 9c4101fdff..03872873f9 100644 --- a/ios/MullvadVPN/Operations/AsyncBlockOperation.swift +++ b/ios/MullvadVPN/Operations/AsyncBlockOperation.swift @@ -79,4 +79,3 @@ class AsyncBlockOperation: AsyncOperation { } } } - diff --git a/ios/MullvadVPN/Operations/AsyncOperation.swift b/ios/MullvadVPN/Operations/AsyncOperation.swift index 1655098af6..b384fd2bee 100644 --- a/ios/MullvadVPN/Operations/AsyncOperation.swift +++ b/ios/MullvadVPN/Operations/AsyncOperation.swift @@ -60,7 +60,7 @@ class AsyncOperation: Operation { /// Backing variable for `_isCancelled`. /// Access must be guarded with `stateLock`. - private var __isCancelled: Bool = false + private var __isCancelled = false /// Backing variable for `error`. /// Access must be guarded with `stateLock`. @@ -113,7 +113,7 @@ class AsyncOperation: Operation { } } - final override var isReady: Bool { + override final var isReady: Bool { stateLock.lock() defer { stateLock.unlock() } @@ -136,19 +136,19 @@ class AsyncOperation: Operation { } } - final override var isExecuting: Bool { + override final var isExecuting: Bool { return state == .executing } - final override var isFinished: Bool { + override final var isFinished: Bool { return state == .finished } - final override var isCancelled: Bool { + override final var isCancelled: Bool { return _isCancelled } - final override var isAsynchronous: Bool { + override final var isAsynchronous: Bool { return true } @@ -237,7 +237,12 @@ class AsyncOperation: Operation { self.dispatchQueue = dispatchQueue ?? DispatchQueue(label: "AsyncOperation.dispatchQueue") super.init() - addObserver(self, forKeyPath: #keyPath(isReady), options: [], context: &Self.observerContext) + addObserver( + self, + forKeyPath: #keyPath(isReady), + options: [], + context: &Self.observerContext + ) } deinit { @@ -251,10 +256,9 @@ class AsyncOperation: Operation { override func observeValue( forKeyPath keyPath: String?, of object: Any?, - change: [NSKeyValueChangeKey : Any]?, + change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer? - ) - { + ) { if context == &Self.observerContext { checkReadiness() return @@ -282,7 +286,7 @@ class AsyncOperation: Operation { // MARK: - Lifecycle - final override func start() { + override final func start() { let currentQueue = OperationQueue.current let underlyingQueue = currentQueue?.underlyingQueue @@ -316,7 +320,7 @@ class AsyncOperation: Operation { // Override in subclasses } - final override func cancel() { + override final func cancel() { var notifyDidCancel = false operationLock.lock() @@ -415,7 +419,6 @@ extension Operation { } } - protocol OperationBlockObserverSupport {} extension AsyncOperation: OperationBlockObserverSupport {} diff --git a/ios/MullvadVPN/Operations/AsyncOperationQueue.swift b/ios/MullvadVPN/Operations/AsyncOperationQueue.swift index 2476f2abd6..86a9cb0891 100644 --- a/ios/MullvadVPN/Operations/AsyncOperationQueue.swift +++ b/ios/MullvadVPN/Operations/AsyncOperationQueue.swift @@ -95,5 +95,4 @@ private final class ExclusivityManager { } } } - } diff --git a/ios/MullvadVPN/Operations/BackgroundObserver.swift b/ios/MullvadVPN/Operations/BackgroundObserver.swift index 6e8e0562c7..b946d03440 100644 --- a/ios/MullvadVPN/Operations/BackgroundObserver.swift +++ b/ios/MullvadVPN/Operations/BackgroundObserver.swift @@ -7,48 +7,47 @@ #if canImport(UIKit) -import UIKit + import UIKit -class BackgroundObserver: OperationObserver { - let name: String - let application: UIApplication - let cancelUponExpiration: Bool + class BackgroundObserver: OperationObserver { + let name: String + let application: UIApplication + let cancelUponExpiration: Bool - private var taskIdentifier: UIBackgroundTaskIdentifier? + private var taskIdentifier: UIBackgroundTaskIdentifier? - init( - application: UIApplication = .shared, - name: String, - cancelUponExpiration: Bool - ) - { - self.application = application - self.name = name - self.cancelUponExpiration = cancelUponExpiration - } + init( + application: UIApplication = .shared, + name: String, + cancelUponExpiration: Bool + ) { + self.application = application + self.name = name + self.cancelUponExpiration = cancelUponExpiration + } - func didAttach(to operation: Operation) { - let expirationHandler = cancelUponExpiration ? { operation.cancel() } : nil + func didAttach(to operation: Operation) { + let expirationHandler = cancelUponExpiration ? { operation.cancel() } : nil - taskIdentifier = application.beginBackgroundTask( - withName: name, - expirationHandler: expirationHandler - ) - } + taskIdentifier = application.beginBackgroundTask( + withName: name, + expirationHandler: expirationHandler + ) + } - func operationDidStart(_ operation: Operation) { - // no-op - } + func operationDidStart(_ operation: Operation) { + // no-op + } - func operationDidCancel(_ operation: Operation) { - // no-op - } + func operationDidCancel(_ operation: Operation) { + // no-op + } - func operationDidFinish(_ operation: Operation, error: Error?) { - if let taskIdentifier = taskIdentifier { - application.endBackgroundTask(taskIdentifier) + func operationDidFinish(_ operation: Operation, error: Error?) { + if let taskIdentifier = taskIdentifier { + application.endBackgroundTask(taskIdentifier) + } } } -} #endif diff --git a/ios/MullvadVPN/Operations/InputInjectionBuilder.swift b/ios/MullvadVPN/Operations/InputInjectionBuilder.swift index 87fff94146..ba4c0ea968 100644 --- a/ios/MullvadVPN/Operations/InputInjectionBuilder.swift +++ b/ios/MullvadVPN/Operations/InputInjectionBuilder.swift @@ -84,7 +84,7 @@ class InputInjectionBuilder<OperationType, Context> where OperationType: InputOp extension InputInjectionBuilder where Context: OperationInputContext, - Context.Input == OperationType.Input + Context.Input == OperationType.Input { func reduce() { reduce { context in diff --git a/ios/MullvadVPN/Operations/OperationCompletion.swift b/ios/MullvadVPN/Operations/OperationCompletion.swift index 5bb73071a3..c4a9f90a88 100644 --- a/ios/MullvadVPN/Operations/OperationCompletion.swift +++ b/ios/MullvadVPN/Operations/OperationCompletion.swift @@ -22,7 +22,7 @@ enum OperationCompletion<Success, Failure: Error> { } var value: Success? { - if case .success(let value) = self { + if case let .success(value) = self { return value } else { return nil @@ -30,7 +30,7 @@ enum OperationCompletion<Success, Failure: Error> { } var error: Failure? { - if case .failure(let error) = self { + if case let .failure(error) = self { return error } else { return nil @@ -39,9 +39,9 @@ enum OperationCompletion<Success, Failure: Error> { var result: Result<Success, Failure>? { switch self { - case .success(let value): + case let .success(value): return .success(value) - case .failure(let error): + case let .failure(error): return .failure(error) case .cancelled: return nil @@ -50,9 +50,9 @@ enum OperationCompletion<Success, Failure: Error> { init(result: Result<Success, Failure>) { switch result { - case .success(let value): + case let .success(value): self = .success(value) - case .failure(let error): + case let .failure(error): self = .failure(error) } } @@ -65,59 +65,70 @@ enum OperationCompletion<Success, Failure: Error> { } } - func map<NewSuccess>(_ block: (Success) -> NewSuccess) -> OperationCompletion<NewSuccess, Failure> { + func map<NewSuccess>(_ block: (Success) -> NewSuccess) + -> OperationCompletion<NewSuccess, Failure> + { switch self { - case .success(let value): + case let .success(value): return .success(block(value)) - case .failure(let error): + case let .failure(error): return .failure(error) case .cancelled: return .cancelled } } - func mapError<NewFailure: Error>(_ block: (Failure) -> NewFailure) -> OperationCompletion<Success, NewFailure> { + func mapError<NewFailure: Error>(_ block: (Failure) -> NewFailure) + -> OperationCompletion<Success, NewFailure> + { switch self { - case .success(let value): + case let .success(value): return .success(value) - case .failure(let error): + case let .failure(error): return .failure(block(error)) case .cancelled: return .cancelled } } - func flatMap<NewSuccess>(_ block: (Success) -> OperationCompletion<NewSuccess, Failure>) -> OperationCompletion<NewSuccess, Failure> { + func flatMap<NewSuccess>(_ block: (Success) -> OperationCompletion<NewSuccess, Failure>) + -> OperationCompletion<NewSuccess, Failure> + { switch self { - case .success(let value): + case let .success(value): return block(value) - case .failure(let error): + case let .failure(error): return .failure(error) case .cancelled: return .cancelled } } - func flatMapError<NewFailure: Error>(_ block: (Failure) -> OperationCompletion<Success, NewFailure>) -> OperationCompletion<Success, NewFailure> { + func flatMapError<NewFailure: Error>( + _ block: (Failure) + -> OperationCompletion<Success, NewFailure> + ) -> OperationCompletion<Success, NewFailure> { switch self { - case .success(let value): + case let .success(value): return .success(value) - case .failure(let error): + case let .failure(error): return block(error) case .cancelled: return .cancelled } } - func tryMap<NewSuccess>(_ block: (Success) throws -> NewSuccess) -> OperationCompletion<NewSuccess, Error> { + func tryMap<NewSuccess>(_ block: (Success) throws -> NewSuccess) + -> OperationCompletion<NewSuccess, Error> + { switch self { - case .success(let value): + case let .success(value): do { return .success(try block(value)) } catch { return .failure(error) } - case .failure(let error): + case let .failure(error): return .failure(error) case .cancelled: return .cancelled diff --git a/ios/MullvadVPN/Operations/OperationCondition.swift b/ios/MullvadVPN/Operations/OperationCondition.swift index 64d4f385fa..024ca52013 100644 --- a/ios/MullvadVPN/Operations/OperationCondition.swift +++ b/ios/MullvadVPN/Operations/OperationCondition.swift @@ -53,7 +53,7 @@ final class NoFailedDependenciesCondition: OperationCondition { return false } - if operation.isCancelled && !self.ignoreCancellations { + if operation.isCancelled, !self.ignoreCancellations { return false } diff --git a/ios/MullvadVPN/Operations/OperationObserver.swift b/ios/MullvadVPN/Operations/OperationObserver.swift index 0cb42e54f1..fd9fb61bfa 100644 --- a/ios/MullvadVPN/Operations/OperationObserver.swift +++ b/ios/MullvadVPN/Operations/OperationObserver.swift @@ -30,8 +30,7 @@ class OperationBlockObserver<OperationType: Operation>: OperationObserver { didStart: VoidBlock? = nil, didCancel: VoidBlock? = nil, didFinish: FinishBlock? = nil - ) - { + ) { _didAttach = didAttach _didStart = didStart _didCancel = didCancel diff --git a/ios/MullvadVPN/Operations/PresentAlertOperation.swift b/ios/MullvadVPN/Operations/PresentAlertOperation.swift index 3c4227b4bd..3a3976de7a 100644 --- a/ios/MullvadVPN/Operations/PresentAlertOperation.swift +++ b/ios/MullvadVPN/Operations/PresentAlertOperation.swift @@ -13,7 +13,11 @@ class PresentAlertOperation: AsyncOperation { private let presentingController: UIViewController private let presentCompletion: (() -> Void)? - init(alertController: UIAlertController, presentingController: UIViewController, presentCompletion: (() -> Void)? = nil) { + init( + alertController: UIAlertController, + presentingController: UIViewController, + presentCompletion: (() -> Void)? = nil + ) { self.alertController = alertController self.presentingController = presentingController self.presentCompletion = presentCompletion @@ -26,7 +30,7 @@ class PresentAlertOperation: AsyncOperation { guard isExecuting else { return } // Guard against dismissing controller during transition. - if !alertController.isBeingPresented && !alertController.isBeingDismissed { + if !alertController.isBeingPresented, !alertController.isBeingDismissed { dismissAndFinish() } } @@ -34,12 +38,12 @@ class PresentAlertOperation: AsyncOperation { override func main() { NotificationCenter.default.addObserver( self, - selector: #selector(self.alertControllerDidDismiss(_:)), + selector: #selector(alertControllerDidDismiss(_:)), name: AlertPresenter.alertControllerDidDismissNotification, - object: self.alertController + object: alertController ) - presentingController.present(self.alertController, animated: true) { + presentingController.present(alertController, animated: true) { self.presentCompletion?() // Alert operation was cancelled during transition? @@ -53,7 +57,7 @@ class PresentAlertOperation: AsyncOperation { NotificationCenter.default.removeObserver( self, name: AlertPresenter.alertControllerDidDismissNotification, - object: self.alertController + object: alertController ) alertController.dismiss(animated: false) { diff --git a/ios/MullvadVPN/Operations/ProductsRequestOperation.swift b/ios/MullvadVPN/Operations/ProductsRequestOperation.swift index 877b52c08c..cc80b9e3f4 100644 --- a/ios/MullvadVPN/Operations/ProductsRequestOperation.swift +++ b/ios/MullvadVPN/Operations/ProductsRequestOperation.swift @@ -9,7 +9,9 @@ import Foundation import StoreKit -class ProductsRequestOperation: ResultOperation<SKProductsResponse, Error>, SKProductsRequestDelegate { +class ProductsRequestOperation: ResultOperation<SKProductsResponse, Error>, + SKProductsRequestDelegate +{ private let productIdentifiers: Set<String> private let maxRetryCount = 10 @@ -78,7 +80,7 @@ class ProductsRequestOperation: ResultOperation<SKProductsResponse, Error>, SKPr self?.finish(completion: .failure(error)) } - retryTimer?.schedule(wallDeadline: .now() + self.retryDelay) + retryTimer?.schedule(wallDeadline: .now() + retryDelay) retryTimer?.activate() } } diff --git a/ios/MullvadVPN/Operations/ResultBlockOperation.swift b/ios/MullvadVPN/Operations/ResultBlockOperation.swift index c59c623002..c9616d958d 100644 --- a/ios/MullvadVPN/Operations/ResultBlockOperation.swift +++ b/ios/MullvadVPN/Operations/ResultBlockOperation.swift @@ -18,8 +18,7 @@ class ResultBlockOperation<Success, Failure: Error>: ResultOperation<Success, Fa convenience init( dispatchQueue: DispatchQueue? = nil, executionBlock: ExecutionBlock? = nil - ) - { + ) { self.init( dispatchQueue: dispatchQueue, executionBlock: executionBlock, @@ -31,8 +30,7 @@ class ResultBlockOperation<Success, Failure: Error>: ResultOperation<Success, Fa convenience init( dispatchQueue: DispatchQueue? = nil, executionBlock: @escaping ThrowingExecutionBlock - ) - { + ) { self.init( dispatchQueue: dispatchQueue, executionBlock: Self.wrapThrowingBlock(executionBlock), @@ -46,8 +44,7 @@ class ResultBlockOperation<Success, Failure: Error>: ResultOperation<Success, Fa executionBlock: ExecutionBlock?, completionQueue: DispatchQueue?, completionHandler: CompletionHandler? - ) - { + ) { self.executionBlock = executionBlock super.init( @@ -99,7 +96,9 @@ class ResultBlockOperation<Success, Failure: Error>: ResultOperation<Success, Fa } } - private class func wrapThrowingBlock(_ executionBlock: @escaping ThrowingExecutionBlock) -> ExecutionBlock { + private class func wrapThrowingBlock(_ executionBlock: @escaping ThrowingExecutionBlock) + -> ExecutionBlock + { return { operation in do { let value = try executionBlock() @@ -113,4 +112,3 @@ class ResultBlockOperation<Success, Failure: Error>: ResultOperation<Success, Fa } } } - diff --git a/ios/MullvadVPN/Operations/ResultOperation.swift b/ios/MullvadVPN/Operations/ResultOperation.swift index dbbb050d4c..4d7ec4b7ce 100644 --- a/ios/MullvadVPN/Operations/ResultOperation.swift +++ b/ios/MullvadVPN/Operations/ResultOperation.swift @@ -63,8 +63,7 @@ class ResultOperation<Success, Failure: Error>: AsyncOperation { dispatchQueue: DispatchQueue?, completionQueue: DispatchQueue?, completionHandler: CompletionHandler? - ) - { + ) { _completionQueue = completionQueue _completionHandler = completionHandler diff --git a/ios/MullvadVPN/Operations/TransformOperation.swift b/ios/MullvadVPN/Operations/TransformOperation.swift index 2a2f5e2b5a..abca23077a 100644 --- a/ios/MullvadVPN/Operations/TransformOperation.swift +++ b/ios/MullvadVPN/Operations/TransformOperation.swift @@ -41,8 +41,7 @@ final class TransformOperation<Input, Output, Failure: Error>: dispatchQueue: DispatchQueue? = nil, input: Input? = nil, block: ExecutionBlock? = nil - ) - { + ) { _input = input executionBlock = block @@ -53,8 +52,7 @@ final class TransformOperation<Input, Output, Failure: Error>: dispatchQueue: DispatchQueue? = nil, input: Input? = nil, throwingBlock: @escaping ThrowingExecutionBlock - ) - { + ) { _input = input executionBlock = Self.wrapThrowingBlock(throwingBlock) @@ -119,7 +117,9 @@ final class TransformOperation<Input, Output, Failure: Error>: } } - private class func wrapThrowingBlock(_ executionBlock: @escaping ThrowingExecutionBlock) -> ExecutionBlock { + private class func wrapThrowingBlock(_ executionBlock: @escaping ThrowingExecutionBlock) + -> ExecutionBlock + { return { input, operation in do { let value = try executionBlock(input) @@ -133,4 +133,3 @@ final class TransformOperation<Input, Output, Failure: Error>: } } } - diff --git a/ios/MullvadVPN/PreferencesDataSource.swift b/ios/MullvadVPN/PreferencesDataSource.swift index 46c0dc5470..6451571bf4 100644 --- a/ios/MullvadVPN/PreferencesDataSource.swift +++ b/ios/MullvadVPN/PreferencesDataSource.swift @@ -105,7 +105,8 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat // Reconfigure cells for items with corresponding DNS entries that were changed during sanitization. let itemsToReload: [Item] = oldDNSDomains.filter { oldDNSEntry in - guard let newDNSEntry = viewModel.dnsEntry(entryIdentifier: oldDNSEntry.identifier) else { return false } + guard let newDNSEntry = viewModel.dnsEntry(entryIdentifier: oldDNSEntry.identifier) + else { return false } return newDNSEntry.address != oldDNSEntry.address }.map { dnsEntry in @@ -124,7 +125,7 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat tableView?.reloadData() } - if !editing && viewModelBeforeEditing != viewModel { + if !editing, viewModelBeforeEditing != viewModel { delegate?.preferencesDataSource(self, didChangeViewModel: viewModel) } } @@ -172,14 +173,18 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat } } - func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + func tableView( + _ tableView: UITableView, + commit editingStyle: UITableViewCell.EditingStyle, + forRowAt indexPath: IndexPath + ) { let item = snapshot.itemForIndexPath(indexPath) if case .addDNSServer = item, editingStyle == .insert { addDNSServerEntry() } - if case .dnsServer(let entryIdentifier) = item, editingStyle == .delete { + if case let .dnsServer(entryIdentifier) = item, editingStyle == .delete { deleteDNSServerEntry(entryIdentifier: entryIdentifier) } } @@ -195,14 +200,19 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat } } - func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { + func tableView( + _ tableView: UITableView, + moveRowAt sourceIndexPath: IndexPath, + to destinationIndexPath: IndexPath + ) { let sourceItem = snapshot.itemForIndexPath(sourceIndexPath)! let destinationItem = snapshot.itemForIndexPath(destinationIndexPath)! - guard case .dnsServer(let sourceIdentifier) = sourceItem, - case .dnsServer(let targetIdentifier) = destinationItem, + guard case let .dnsServer(sourceIdentifier) = sourceItem, + case let .dnsServer(targetIdentifier) = destinationItem, let sourceIndex = viewModel.indexOfDNSEntry(entryIdentifier: sourceIdentifier), - let destinationIndex = viewModel.indexOfDNSEntry(entryIdentifier: targetIdentifier) else { return } + let destinationIndex = viewModel.indexOfDNSEntry(entryIdentifier: targetIdentifier) + else { return } let removedEntry = viewModel.customDNSDomains.remove(at: sourceIndex) viewModel.customDNSDomains.insert(removedEntry, at: destinationIndex) @@ -217,7 +227,11 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - return tableView.dequeueReusableHeaderFooterView(withIdentifier: HeaderFooterReuseIdentifiers.spacer.rawValue) + return tableView + .dequeueReusableHeaderFooterView( + withIdentifier: HeaderFooterReuseIdentifiers.spacer + .rawValue + ) } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { @@ -228,7 +242,11 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat return nil case .customDNS: - let reusableView = tableView.dequeueReusableHeaderFooterView(withIdentifier: HeaderFooterReuseIdentifiers.customDNSFooter.rawValue) as! SettingsStaticTextFooterView + let reusableView = tableView + .dequeueReusableHeaderFooterView( + withIdentifier: HeaderFooterReuseIdentifiers + .customDNSFooter.rawValue + ) as! SettingsStaticTextFooterView configureFooterView(reusableView) return reusableView } @@ -255,7 +273,10 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat } } - func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { + func tableView( + _ tableView: UITableView, + editingStyleForRowAt indexPath: IndexPath + ) -> UITableViewCell.EditingStyle { let item = snapshot.itemForIndexPath(indexPath) switch item { @@ -268,7 +289,11 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat } } - func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath { + func tableView( + _ tableView: UITableView, + targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, + toProposedIndexPath proposedDestinationIndexPath: IndexPath + ) -> IndexPath { guard let sectionIdentifier = snapshot.section(at: sourceIndexPath.section), case .customDNS = sectionIdentifier else { return sourceIndexPath } @@ -300,18 +325,27 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat private func registerClasses() { CellReuseIdentifiers.allCases.forEach { enumCase in - tableView?.register(enumCase.reusableViewClass, forCellReuseIdentifier: enumCase.rawValue) + tableView?.register( + enumCase.reusableViewClass, + forCellReuseIdentifier: enumCase.rawValue + ) } HeaderFooterReuseIdentifiers.allCases.forEach { enumCase in - tableView?.register(enumCase.reusableViewClass, forHeaderFooterViewReuseIdentifier: enumCase.rawValue) + tableView?.register( + enumCase.reusableViewClass, + forHeaderFooterViewReuseIdentifier: enumCase.rawValue + ) } } private func updateSnapshot() { var newSnapshot = DataSourceSnapshot<Section, Item>() newSnapshot.appendSections([.mullvadDNS, .customDNS]) - newSnapshot.appendItems([.blockAdvertising, .blockTracking, .blockMalware, .blockAdultContent, .blockGambling], in: .mullvadDNS) + newSnapshot.appendItems( + [.blockAdvertising, .blockTracking, .blockMalware, .blockAdultContent, .blockGambling], + in: .mullvadDNS + ) newSnapshot.appendItems([.useCustomDNS], in: .customDNS) let dnsServerItems = viewModel.customDNSDomains.map { entry in @@ -319,17 +353,24 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat } newSnapshot.appendItems(dnsServerItems, in: .customDNS) - if isEditing && viewModel.customDNSDomains.count < DNSSettings.maxAllowedCustomDNSDomains { + if isEditing, viewModel.customDNSDomains.count < DNSSettings.maxAllowedCustomDNSDomains { newSnapshot.appendItems([.addDNSServer], in: .customDNS) } snapshot = newSnapshot } - private func dequeueCellForItem(_ item: Item, in tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { + private func dequeueCellForItem( + _ item: Item, + in tableView: UITableView, + at indexPath: IndexPath + ) -> UITableViewCell { switch item { case .blockAdvertising: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, for: indexPath) as! SettingsSwitchCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, + for: indexPath + ) as! SettingsSwitchCell cell.titleLabel.text = NSLocalizedString( "BLOCK_ADS_CELL_LABEL", @@ -346,7 +387,10 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat return cell case .blockTracking: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, for: indexPath) as! SettingsSwitchCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, + for: indexPath + ) as! SettingsSwitchCell cell.titleLabel.text = NSLocalizedString( "BLOCK_TRACKERS_CELL_LABEL", @@ -363,7 +407,10 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat return cell case .blockMalware: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, for: indexPath) as! SettingsSwitchCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, + for: indexPath + ) as! SettingsSwitchCell cell.titleLabel.text = NSLocalizedString( "BLOCK_MALWARE_CELL_LABEL", @@ -380,7 +427,10 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat return cell case .blockAdultContent: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, for: indexPath) as! SettingsSwitchCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, + for: indexPath + ) as! SettingsSwitchCell cell.titleLabel.text = NSLocalizedString( "BLOCK_ADULT_CELL_LABEL", @@ -397,7 +447,10 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat return cell case .blockGambling: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, for: indexPath) as! SettingsSwitchCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, + for: indexPath + ) as! SettingsSwitchCell cell.titleLabel.text = NSLocalizedString( "BLOCK_GAMBLING_CELL_LABEL", @@ -414,7 +467,10 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat return cell case .useCustomDNS: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, for: indexPath) as! SettingsSwitchCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.settingSwitch.rawValue, + for: indexPath + ) as! SettingsSwitchCell cell.titleLabel.text = NSLocalizedString( "CUSTOM_DNS_CELL_LABEL", @@ -428,12 +484,16 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat self?.setEnableCustomDNS(isOn) } - cell.accessibilityHint = viewModel.customDNSPrecondition.localizedDescription(isEditing: isEditing) + cell.accessibilityHint = viewModel.customDNSPrecondition + .localizedDescription(isEditing: isEditing) return cell case .addDNSServer: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.addDNSServer.rawValue, for: indexPath) as! SettingsAddDNSEntryCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.addDNSServer.rawValue, + for: indexPath + ) as! SettingsAddDNSEntryCell cell.titleLabel.text = NSLocalizedString( "ADD_CUSTOM_DNS_SERVER_CELL_LABEL", tableName: "Preferences", @@ -447,17 +507,23 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat return cell - case .dnsServer(let entryIdentifier): + case let .dnsServer(entryIdentifier): let dnsServerEntry = viewModel.dnsEntry(entryIdentifier: entryIdentifier)! - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.dnsServer.rawValue, for: indexPath) as! SettingsDNSTextCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.dnsServer.rawValue, + for: indexPath + ) as! SettingsDNSTextCell cell.textField.text = dnsServerEntry.address cell.isValidInput = viewModel.validateDNSDomainUserInput(dnsServerEntry.address) cell.onTextChange = { [weak self] cell in - guard let self = self, let indexPath = self.tableView?.indexPath(for: cell) else { return } + guard let self = self, + let indexPath = self.tableView?.indexPath(for: cell) else { return } - if case .dnsServer(let entryIdentifier) = self.snapshot.itemForIndexPath(indexPath) { + if case let .dnsServer(entryIdentifier) = self.snapshot + .itemForIndexPath(indexPath) + { self.handleDNSEntryChange(entryIdentifier: entryIdentifier, cell: cell) } } @@ -581,14 +647,16 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat if completed { // Focus on the new entry text field. let lastDNSEntry = self.snapshot.items(in: .customDNS).last { item in - if case .dnsServer(let entryIdentifier) = item { + if case let .dnsServer(entryIdentifier) = item { return entryIdentifier == newDNSEntry.identifier } else { return false } } - if let lastDNSEntry = lastDNSEntry, let indexPath = self.snapshot.indexPathForItem(lastDNSEntry) { + if let lastDNSEntry = lastDNSEntry, + let indexPath = self.snapshot.indexPathForItem(lastDNSEntry) + { let cell = self.tableView?.cellForRow(at: indexPath) as? SettingsDNSTextCell self.tableView?.scrollToRow(at: indexPath, at: .bottom, animated: true) @@ -629,7 +697,9 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat // Reload footer view tableView?.performBatchUpdates { - if let reusableView = tableView?.footerView(forSection: sectionIndex) as? SettingsStaticTextFooterView { + if let reusableView = tableView? + .footerView(forSection: sectionIndex) as? SettingsStaticTextFooterView + { configureFooterView(reusableView) } } @@ -647,5 +717,4 @@ class PreferencesDataSource: NSObject, UITableViewDataSource, UITableViewDelegat reusableView.titleLabel.attributedText = viewModel.customDNSPrecondition .attributedLocalizedDescription(isEditing: isEditing, preferredFont: font) } - } diff --git a/ios/MullvadVPN/PreferencesDataSourceDelegate.swift b/ios/MullvadVPN/PreferencesDataSourceDelegate.swift index 1de9aef42b..84f18d93e8 100644 --- a/ios/MullvadVPN/PreferencesDataSourceDelegate.swift +++ b/ios/MullvadVPN/PreferencesDataSourceDelegate.swift @@ -9,5 +9,8 @@ import Foundation protocol PreferencesDataSourceDelegate: AnyObject { - func preferencesDataSource(_ dataSource: PreferencesDataSource, didChangeViewModel viewModel: PreferencesViewModel) + func preferencesDataSource( + _ dataSource: PreferencesDataSource, + didChangeViewModel viewModel: PreferencesViewModel + ) } diff --git a/ios/MullvadVPN/PreferencesViewController.swift b/ios/MullvadVPN/PreferencesViewController.swift index 05fdb793fd..30edd298ba 100644 --- a/ios/MullvadVPN/PreferencesViewController.swift +++ b/ios/MullvadVPN/PreferencesViewController.swift @@ -6,11 +6,12 @@ // Copyright © 2021 Mullvad VPN AB. All rights reserved. // -import UIKit import Logging +import UIKit -class PreferencesViewController: UITableViewController, PreferencesDataSourceDelegate, TunnelObserver { - +class PreferencesViewController: UITableViewController, PreferencesDataSourceDelegate, + TunnelObserver +{ private let dataSource = PreferencesDataSource() override var preferredStatusBarStyle: UIStatusBarStyle { @@ -65,7 +66,10 @@ class PreferencesViewController: UITableViewController, PreferencesDataSourceDel // MARK: - PreferencesDataSourceDelegate - func preferencesDataSource(_ dataSource: PreferencesDataSource, didChangeViewModel dataModel: PreferencesViewModel) { + func preferencesDataSource( + _ dataSource: PreferencesDataSource, + didChangeViewModel dataModel: PreferencesViewModel + ) { let dnsSettings = dataModel.asDNSSettings() TunnelManager.shared.setDNSSettings(dnsSettings) @@ -85,13 +89,14 @@ class PreferencesViewController: UITableViewController, PreferencesDataSourceDel // no-op } - func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2) { + func tunnelManager( + _ manager: TunnelManager, + didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2 + ) { dataSource.update(from: tunnelSettings.dnsSettings) } - func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState) { // no-op } - } diff --git a/ios/MullvadVPN/PreferencesViewModel.swift b/ios/MullvadVPN/PreferencesViewModel.swift index 123331f4a8..4a164dca84 100644 --- a/ios/MullvadVPN/PreferencesViewModel.swift +++ b/ios/MullvadVPN/PreferencesViewModel.swift @@ -27,7 +27,10 @@ enum CustomDNSPrecondition { } /// Returns attributed localized description explaining how to enable Custom DNS. - func attributedLocalizedDescription(isEditing: Bool, preferredFont: UIFont) -> NSAttributedString? { + func attributedLocalizedDescription( + isEditing: Bool, + preferredFont: UIFont + ) -> NSAttributedString? { switch self { case .satisfied: return nil diff --git a/ios/MullvadVPN/ProblemReportReviewViewController.swift b/ios/MullvadVPN/ProblemReportReviewViewController.swift index 19fc05a248..cbe2956746 100644 --- a/ios/MullvadVPN/ProblemReportReviewViewController.swift +++ b/ios/MullvadVPN/ProblemReportReviewViewController.swift @@ -9,12 +9,15 @@ import UIKit class ProblemReportReviewViewController: UIViewController { - private var textView = UITextView() private let reportString: String private var dismissButtonItem: UIBarButtonItem { - return UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDismissButton(_:))) + return UIBarButtonItem( + barButtonSystemItem: .done, + target: self, + action: #selector(handleDismissButton(_:)) + ) } init(reportString: String) { @@ -51,7 +54,7 @@ class ProblemReportReviewViewController: UIViewController { textView.topAnchor.constraint(equalTo: view.topAnchor), textView.leadingAnchor.constraint(equalTo: view.leadingAnchor), textView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - textView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + textView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) // Used to layout constraints so that navigation controller could properly adjust the text diff --git a/ios/MullvadVPN/ProblemReportSubmissionOverlayView.swift b/ios/MullvadVPN/ProblemReportSubmissionOverlayView.swift index fadff38f70..2274606044 100644 --- a/ios/MullvadVPN/ProblemReportSubmissionOverlayView.swift +++ b/ios/MullvadVPN/ProblemReportSubmissionOverlayView.swift @@ -6,11 +6,10 @@ // Copyright © 2021 Mullvad VPN AB. All rights reserved. // -import UIKit import Foundation +import UIKit class ProblemReportSubmissionOverlayView: UIView { - var editButtonAction: (() -> Void)? var retryButtonAction: (() -> Void)? @@ -49,7 +48,7 @@ class ProblemReportSubmissionOverlayView: UIView { switch self { case .sending: return nil - case .sent(let email): + case let .sent(email): let combinedAttributedString = NSMutableAttributedString( string: NSLocalizedString( "THANKS_MESSAGE", @@ -79,7 +78,8 @@ class ProblemReportSubmissionOverlayView: UIView { tableName: "ProblemReport", value: "If needed we will contact you at %@", comment: "" - ), email) + ), email + ) let emailAttributedString = NSMutableAttributedString(string: emailText) if let emailRange = emailText.range(of: email) { let font = UIFont.systemFont(ofSize: 17, weight: .bold) @@ -94,7 +94,7 @@ class ProblemReportSubmissionOverlayView: UIView { return combinedAttributedString - case .failure(let error): + case let .failure(error): return error.errorChainDescription.flatMap { NSAttributedString(string: $0) } } } @@ -102,7 +102,7 @@ class ProblemReportSubmissionOverlayView: UIView { var state: State = .sending { didSet { - transitionToState(self.state) + transitionToState(state) } } @@ -111,6 +111,7 @@ class ProblemReportSubmissionOverlayView: UIView { indicator.tintColor = .white return indicator }() + let statusImageView = StatusImageView(style: .success) let titleLabel: UILabel = { @@ -179,13 +180,22 @@ class ProblemReportSubmissionOverlayView: UIView { } private func addSubviews() { - for subview in [titleLabel, bodyLabel, activityIndicator, statusImageView, buttonsStackView] { + for subview in [ + titleLabel, + bodyLabel, + activityIndicator, + statusImageView, + buttonsStackView, + ] { subview.translatesAutoresizingMaskIntoConstraints = false addSubview(subview) } NSLayoutConstraint.activate([ - statusImageView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 32), + statusImageView.topAnchor.constraint( + equalTo: layoutMarginsGuide.topAnchor, + constant: 32 + ), statusImageView.centerXAnchor.constraint(equalTo: centerXAnchor), activityIndicator.centerXAnchor.constraint(equalTo: statusImageView.centerXAnchor), @@ -195,14 +205,20 @@ class ProblemReportSubmissionOverlayView: UIView { titleLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), titleLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - bodyLabel.topAnchor.constraint(equalToSystemSpacingBelow: titleLabel.bottomAnchor, multiplier: 1), + bodyLabel.topAnchor.constraint( + equalToSystemSpacingBelow: titleLabel.bottomAnchor, + multiplier: 1 + ), bodyLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), bodyLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - buttonsStackView.topAnchor.constraint(greaterThanOrEqualTo: bodyLabel.bottomAnchor, constant: 18), + buttonsStackView.topAnchor.constraint( + greaterThanOrEqualTo: bodyLabel.bottomAnchor, + constant: 18 + ), buttonsStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), buttonsStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - buttonsStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + buttonsStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), ]) } diff --git a/ios/MullvadVPN/ProblemReportViewController.swift b/ios/MullvadVPN/ProblemReportViewController.swift index 0087faa18f..a48da7ba24 100644 --- a/ios/MullvadVPN/ProblemReportViewController.swift +++ b/ios/MullvadVPN/ProblemReportViewController.swift @@ -9,7 +9,6 @@ import UIKit class ProblemReportViewController: UIViewController, UITextFieldDelegate, ConditionalNavigation { - private let apiProxy = REST.ProxyFactory.shared.createAPIProxy() private var textViewKeyboardResponder: AutomaticKeyboardResponder? @@ -167,13 +166,15 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit return button }() - private lazy var emailAccessoryToolbar: UIToolbar = { - return makeKeyboardToolbar(canGoBackward: false, canGoForward: true) - }() + private lazy var emailAccessoryToolbar: UIToolbar = makeKeyboardToolbar( + canGoBackward: false, + canGoForward: true + ) - private lazy var messageAccessoryToolbar: UIToolbar = { - return makeKeyboardToolbar(canGoBackward: true, canGoForward: false) - }() + private lazy var messageAccessoryToolbar: UIToolbar = makeKeyboardToolbar( + canGoBackward: true, + canGoForward: false + ) private lazy var submissionOverlayView: ProblemReportSubmissionOverlayView = { let overlay = ProblemReportSubmissionOverlayView() @@ -246,8 +247,8 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit override func viewSafeAreaInsetsDidChange() { super.viewSafeAreaInsetsDidChange() - self.scrollViewKeyboardResponder?.updateContentInsets() - self.textViewKeyboardResponder?.updateContentInsets() + scrollViewKeyboardResponder?.updateContentInsets() + textViewKeyboardResponder?.updateContentInsets() } // MARK: - Actions @@ -270,7 +271,7 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit } if Self.persistentViewModel.email.isEmpty { - presentEmptyEmailConfirmationAlert { (shouldSend) in + presentEmptyEmailConfirmationAlert { shouldSend in if shouldSend { proceedWithSubmission() } @@ -281,7 +282,10 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit } @objc func handleViewLogsButtonTap() { - let reviewController = ProblemReportReviewViewController(reportString: consolidatedLog.string) + let reviewController = ProblemReportReviewViewController( + reportString: consolidatedLog + .string + ) let navigationController = UINavigationController(rootViewController: reviewController) present(navigationController, animated: true) @@ -291,22 +295,34 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit private func registerForNotifications() { let notificationCenter = NotificationCenter.default - notificationCenter.addObserver(self, selector: #selector(emailTextFieldDidChange), - name: UITextField.textDidChangeNotification, - object: emailTextField) - notificationCenter.addObserver(self, selector: #selector(messageTextViewDidBeginEditing), - name: UITextView.textDidBeginEditingNotification, - object: messageTextView) - notificationCenter.addObserver(self, selector: #selector(messageTextViewDidEndEditing), - name: UITextView.textDidEndEditingNotification, - object: messageTextView) - notificationCenter.addObserver(self, selector: #selector(messageTextViewDidChange), - name: UITextView.textDidChangeNotification, - object: messageTextView) + notificationCenter.addObserver( + self, + selector: #selector(emailTextFieldDidChange), + name: UITextField.textDidChangeNotification, + object: emailTextField + ) + notificationCenter.addObserver( + self, + selector: #selector(messageTextViewDidBeginEditing), + name: UITextView.textDidBeginEditingNotification, + object: messageTextView + ) + notificationCenter.addObserver( + self, + selector: #selector(messageTextViewDidEndEditing), + name: UITextView.textDidEndEditingNotification, + object: messageTextView + ) + notificationCenter.addObserver( + self, + selector: #selector(messageTextViewDidChange), + name: UITextView.textDidChangeNotification, + object: messageTextView + ) } private func makeKeyboardToolbar(canGoBackward: Bool, canGoForward: Bool) -> UIToolbar { - var toolbarItems = UIBarButtonItem.makeKeyboardNavigationItems { (prevButton, nextButton) in + var toolbarItems = UIBarButtonItem.makeKeyboardNavigationItems { prevButton, nextButton in prevButton.target = self prevButton.action = #selector(focusEmailTextField) prevButton.isEnabled = canGoBackward @@ -318,7 +334,11 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit toolbarItems.append(contentsOf: [ UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil), - UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard)) + UIBarButtonItem( + barButtonSystemItem: .done, + target: self, + action: #selector(dismissKeyboard) + ), ]) let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 100, height: 44)) @@ -327,39 +347,59 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit } private func addConstraints() { - self.activeMessageTextViewConstraints = [ + activeMessageTextViewConstraints = [ messageTextView.topAnchor.constraint(equalTo: view.topAnchor), messageTextView.leadingAnchor.constraint(equalTo: view.leadingAnchor), messageTextView.trailingAnchor.constraint(equalTo: view.trailingAnchor), messageTextView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ] - self.inactiveMessageTextViewConstraints = [ - messageTextView.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 12), + inactiveMessageTextViewConstraints = [ + messageTextView.topAnchor.constraint( + equalTo: emailTextField.bottomAnchor, + constant: 12 + ), messageTextView.leadingAnchor.constraint(equalTo: textFieldsHolder.leadingAnchor), messageTextView.trailingAnchor.constraint(equalTo: textFieldsHolder.trailingAnchor), messageTextView.bottomAnchor.constraint(equalTo: textFieldsHolder.bottomAnchor), ] var constraints = [ - subheaderLabel.topAnchor.constraint(equalTo: containerView.layoutMarginsGuide.topAnchor), - subheaderLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor), - subheaderLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor), + subheaderLabel.topAnchor + .constraint(equalTo: containerView.layoutMarginsGuide.topAnchor), + subheaderLabel.leadingAnchor + .constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor), + subheaderLabel.trailingAnchor + .constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor), - textFieldsHolder.topAnchor.constraint(equalTo: subheaderLabel.bottomAnchor, constant: 24), - textFieldsHolder.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor), - textFieldsHolder.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor), + textFieldsHolder.topAnchor.constraint( + equalTo: subheaderLabel.bottomAnchor, + constant: 24 + ), + textFieldsHolder.leadingAnchor + .constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor), + textFieldsHolder.trailingAnchor + .constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor), - buttonsStackView.topAnchor.constraint(equalTo: textFieldsHolder.bottomAnchor, constant: 18), - buttonsStackView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor), - buttonsStackView.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor), - buttonsStackView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor), + buttonsStackView.topAnchor.constraint( + equalTo: textFieldsHolder.bottomAnchor, + constant: 18 + ), + buttonsStackView.leadingAnchor + .constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor), + buttonsStackView.trailingAnchor + .constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor), + buttonsStackView.bottomAnchor + .constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor), emailTextField.topAnchor.constraint(equalTo: textFieldsHolder.topAnchor), emailTextField.leadingAnchor.constraint(equalTo: textFieldsHolder.leadingAnchor), emailTextField.trailingAnchor.constraint(equalTo: textFieldsHolder.trailingAnchor), - messagePlaceholder.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 12), + messagePlaceholder.topAnchor.constraint( + equalTo: emailTextField.bottomAnchor, + constant: 12 + ), messagePlaceholder.leadingAnchor.constraint(equalTo: textFieldsHolder.leadingAnchor), messagePlaceholder.trailingAnchor.constraint(equalTo: textFieldsHolder.trailingAnchor), messagePlaceholder.bottomAnchor.constraint(equalTo: textFieldsHolder.bottomAnchor), @@ -371,44 +411,49 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit scrollView.frameLayoutGuide.trailingAnchor.constraint(equalTo: view.trailingAnchor), scrollView.contentLayoutGuide.topAnchor.constraint(equalTo: containerView.topAnchor), - scrollView.contentLayoutGuide.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), - scrollView.contentLayoutGuide.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - scrollView.contentLayoutGuide.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), + scrollView.contentLayoutGuide.bottomAnchor + .constraint(equalTo: containerView.bottomAnchor), + scrollView.contentLayoutGuide.leadingAnchor + .constraint(equalTo: containerView.leadingAnchor), + scrollView.contentLayoutGuide.trailingAnchor + .constraint(equalTo: containerView.trailingAnchor), - scrollView.contentLayoutGuide.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor), - scrollView.contentLayoutGuide.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.heightAnchor), + scrollView.contentLayoutGuide.widthAnchor + .constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor), + scrollView.contentLayoutGuide.heightAnchor + .constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.heightAnchor), messageTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: 150), ] - constraints.append(contentsOf: self.inactiveMessageTextViewConstraints) + constraints.append(contentsOf: inactiveMessageTextViewConstraints) NSLayoutConstraint.activate(constraints) } private func setDescriptionFieldExpanded(_ isExpanded: Bool) { // Make voice over ignore siblings when expanded - self.messageTextView.accessibilityViewIsModal = isExpanded + messageTextView.accessibilityViewIsModal = isExpanded if isExpanded { // Disable the large title - self.navigationItem.largeTitleDisplayMode = .never + navigationItem.largeTitleDisplayMode = .never // Move the text view above scroll view - view.addSubview(self.messageTextView) + view.addSubview(messageTextView) // Re-add old constraints - NSLayoutConstraint.activate(self.inactiveMessageTextViewConstraints) + NSLayoutConstraint.activate(inactiveMessageTextViewConstraints) // Do a layout pass view.layoutIfNeeded() // Swap constraints - NSLayoutConstraint.deactivate(self.inactiveMessageTextViewConstraints) - NSLayoutConstraint.activate(self.activeMessageTextViewConstraints) + NSLayoutConstraint.deactivate(inactiveMessageTextViewConstraints) + NSLayoutConstraint.activate(activeMessageTextViewConstraints) // Enable content inset adjustment on text view - self.messageTextView.contentInsetAdjustmentBehavior = .always + messageTextView.contentInsetAdjustmentBehavior = .always // Animate constraints & rounded corners on the text view animateDescriptionTextView(animations: { @@ -416,7 +461,7 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit self.messageTextView.roundCorners = false self.view.layoutIfNeeded() - }) { (completed) in + }) { completed in self.isMessageTextViewExpanded = true self.textViewKeyboardResponder?.updateContentInsets() @@ -427,11 +472,11 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit } else { // Re-enable the large title - self.navigationItem.largeTitleDisplayMode = .automatic + navigationItem.largeTitleDisplayMode = .automatic // Swap constraints - NSLayoutConstraint.deactivate(self.activeMessageTextViewConstraints) - NSLayoutConstraint.activate(self.inactiveMessageTextViewConstraints) + NSLayoutConstraint.deactivate(activeMessageTextViewConstraints) + NSLayoutConstraint.activate(inactiveMessageTextViewConstraints) // Animate constraints & rounded corners on the text view animateDescriptionTextView(animations: { @@ -439,7 +484,7 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit self.messageTextView.roundCorners = true self.view.layoutIfNeeded() - }) { (completed) in + }) { completed in // Revert the content adjustment behavior self.messageTextView.contentInsetAdjustmentBehavior = .never @@ -454,8 +499,11 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit } } - private func animateDescriptionTextView(animations: @escaping () -> Void, completion: @escaping (Bool) -> Void) { - UIView.animate(withDuration: 0.25, animations: animations) { (completed) in + private func animateDescriptionTextView( + animations: @escaping () -> Void, + completion: @escaping (Bool) -> Void + ) { + UIView.animate(withDuration: 0.25, animations: animations) { completed in completion(completed) } } @@ -468,7 +516,11 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit comment: "" ) - let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) + let alertController = UIAlertController( + title: nil, + message: message, + preferredStyle: .alert + ) let cancelAction = UIAlertAction( title: NSLocalizedString( @@ -506,18 +558,26 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit private func showSubmissionOverlay() { guard !showsSubmissionOverlay else { return } - self.showsSubmissionOverlay = true + showsSubmissionOverlay = true view.addSubview(submissionOverlayView) NSLayoutConstraint.activate([ submissionOverlayView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - submissionOverlayView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), - submissionOverlayView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), - submissionOverlayView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + submissionOverlayView.leadingAnchor + .constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + submissionOverlayView.trailingAnchor + .constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + submissionOverlayView.bottomAnchor + .constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), ]) - UIView.transition(from: scrollView, to: submissionOverlayView, duration: 0.25, options: [.showHideTransitionViews, .transitionCrossDissolve]) { (success) in + UIView.transition( + from: scrollView, + to: submissionOverlayView, + duration: 0.25, + options: [.showHideTransitionViews, .transitionCrossDissolve] + ) { success in // success } } @@ -525,9 +585,14 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit private func hideSubmissionOverlay() { guard showsSubmissionOverlay else { return } - self.showsSubmissionOverlay = false + showsSubmissionOverlay = false - UIView.transition(from: submissionOverlayView, to: scrollView, duration: 0.25, options: [.showHideTransitionViews, .transitionCrossDissolve]) { (success) in + UIView.transition( + from: submissionOverlayView, + to: scrollView, + duration: 0.25, + options: [.showHideTransitionViews, .transitionCrossDissolve] + ) { success in // success self.submissionOverlayView.removeFromSuperview() } @@ -591,7 +656,10 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit navigationItem.setHidesBackButton(true, animated: true) } - private func didSendProblemReport(viewModel: ViewModel, completion: OperationCompletion<(), REST.Error>) { + private func didSendProblemReport( + viewModel: ViewModel, + completion: OperationCompletion<Void, REST.Error> + ) { switch completion { case .success: submissionOverlayView.state = .sent(viewModel.email) @@ -599,7 +667,7 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit // Clear persistent view model upon successful submission clearPersistentViewModel() - case .failure(let error): + case let .failure(error): submissionOverlayView.state = .failure(error) case .cancelled: @@ -615,11 +683,16 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit let viewModel = Self.persistentViewModel let log = consolidatedLog.string - let metadata = consolidatedLog.metadata.reduce(into: [:]) { (output, entry) in + let metadata = consolidatedLog.metadata.reduce(into: [:]) { output, entry in output[entry.key.rawValue] = entry.value } - let request = REST.ProblemReportRequest(address: viewModel.email, message: viewModel.message, log: log, metadata: metadata) + let request = REST.ProblemReportRequest( + address: viewModel.email, + message: viewModel.message, + log: log, + metadata: metadata + ) willSendProblemReport() @@ -655,7 +728,10 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit // MARK: - ConditionalNavigation - func shouldPopNavigationItem(_ navigationItem: UINavigationItem, trigger: NavigationPopTrigger) -> Bool { + func shouldPopNavigationItem( + _ navigationItem: UINavigationItem, + trigger: NavigationPopTrigger + ) -> Bool { switch trigger { case .interactiveGesture: // Disable swipe when editing @@ -667,5 +743,4 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit return true } } - } diff --git a/ios/MullvadVPN/REST/HTTP.swift b/ios/MullvadVPN/REST/HTTP.swift index 82178dc2f5..85995c43ce 100644 --- a/ios/MullvadVPN/REST/HTTP.swift +++ b/ios/MullvadVPN/REST/HTTP.swift @@ -27,7 +27,7 @@ struct HTTPStatus: RawRepresentable, Equatable { static let notFound = HTTPStatus(rawValue: 404) static func isSuccess(_ code: Int) -> Bool { - return (200..<300).contains(code) + return (200 ..< 300).contains(code) } let rawValue: Int @@ -54,7 +54,7 @@ extension HTTPURLResponse { if #available(iOS 13.0, *) { return self.value(forHTTPHeaderField: headerField) } else { - for case let key as String in self.allHeaderFields.keys { + for case let key as String in allHeaderFields.keys { if case .orderedSame = key.caseInsensitiveCompare(headerField) { return self.allHeaderFields[key] as? String } diff --git a/ios/MullvadVPN/REST/RESTAPIProxy.swift b/ios/MullvadVPN/REST/RESTAPIProxy.swift index 1d7f259b4b..a53b95b8ac 100644 --- a/ios/MullvadVPN/REST/RESTAPIProxy.swift +++ b/ios/MullvadVPN/REST/RESTAPIProxy.swift @@ -8,8 +8,8 @@ import Foundation import Network -import class WireGuardKitTypes.PublicKey import struct WireGuardKitTypes.IPAddressRange +import class WireGuardKitTypes.PublicKey extension REST { class APIProxy: Proxy<ProxyConfiguration> { @@ -28,8 +28,7 @@ extension REST { func getAddressList( retryStrategy: REST.RetryStrategy, completionHandler: @escaping CompletionHandler<[AnyIPEndpoint]> - ) -> Cancellable - { + ) -> Cancellable { let requestHandler = AnyRequestHandler { endpoint in return try self.requestFactory.createRequest( endpoint: endpoint, @@ -56,8 +55,7 @@ extension REST { etag: String?, retryStrategy: REST.RetryStrategy, completionHandler: @escaping CompletionHandler<ServerRelaysCacheResponse> - ) -> Cancellable - { + ) -> Cancellable { let requestHandler = AnyRequestHandler { endpoint in var requestBuilder = try self.requestFactory.createRequestBuilder( endpoint: endpoint, @@ -72,33 +70,35 @@ extension REST { return requestBuilder.getRequest() } - let responseHandler = AnyResponseHandler { response, data -> ResponseHandlerResult<ServerRelaysCacheResponse> in - let httpStatus = HTTPStatus(rawValue: response.statusCode) + let responseHandler = + AnyResponseHandler { response, data -> ResponseHandlerResult<ServerRelaysCacheResponse> in + let httpStatus = HTTPStatus(rawValue: response.statusCode) - switch httpStatus { - case let httpStatus where httpStatus.isSuccess: - return .decoding { - let serverRelays = try self.responseDecoder.decode( - ServerRelaysResponse.self, - from: data - ) - let newEtag = response.value(forCaseInsensitiveHTTPHeaderField: HTTPHeader.etag) + switch httpStatus { + case let httpStatus where httpStatus.isSuccess: + return .decoding { + let serverRelays = try self.responseDecoder.decode( + ServerRelaysResponse.self, + from: data + ) + let newEtag = response + .value(forCaseInsensitiveHTTPHeaderField: HTTPHeader.etag) - return .newContent(newEtag, serverRelays) - } + return .newContent(newEtag, serverRelays) + } - case .notModified where etag != nil: - return .success(.notModified) + case .notModified where etag != nil: + return .success(.notModified) - default: - return .unhandledResponse( - try? self.responseDecoder.decode( - ServerErrorResponse.self, - from: data + default: + return .unhandledResponse( + try? self.responseDecoder.decode( + ServerErrorResponse.self, + from: data + ) ) - ) + } } - } return addOperation( name: "get-relays", @@ -114,8 +114,7 @@ extension REST { receiptString: Data, retryStrategy: REST.RetryStrategy, completionHandler: @escaping CompletionHandler<CreateApplePaymentResponse> - ) -> Cancellable - { + ) -> Cancellable { let requestHandler = AnyRequestHandler { endpoint in var requestBuilder = try self.requestFactory .createRequestBuilder( @@ -133,28 +132,32 @@ extension REST { return requestBuilder.getRequest() } - let responseHandler = AnyResponseHandler { response, data -> ResponseHandlerResult<CreateApplePaymentResponse> in - if HTTPStatus.isSuccess(response.statusCode) { - return .decoding { - let serverResponse = try self.responseDecoder.decode( - CreateApplePaymentRawResponse.self, - from: data - ) - if serverResponse.timeAdded > 0 { - return .timeAdded(serverResponse.timeAdded, serverResponse.newExpiry) - } else { - return .noTimeAdded(serverResponse.newExpiry) + let responseHandler = + AnyResponseHandler { response, data -> ResponseHandlerResult<CreateApplePaymentResponse> in + if HTTPStatus.isSuccess(response.statusCode) { + return .decoding { + let serverResponse = try self.responseDecoder.decode( + CreateApplePaymentRawResponse.self, + from: data + ) + if serverResponse.timeAdded > 0 { + return .timeAdded( + serverResponse.timeAdded, + serverResponse.newExpiry + ) + } else { + return .noTimeAdded(serverResponse.newExpiry) + } } - } - } else { - return .unhandledResponse( - try? self.responseDecoder.decode( - ServerErrorResponse.self, - from: data + } else { + return .unhandledResponse( + try? self.responseDecoder.decode( + ServerErrorResponse.self, + from: data + ) ) - ) + } } - } return addOperation( name: "create-apple-payment", @@ -169,8 +172,7 @@ extension REST { _ body: ProblemReportRequest, retryStrategy: REST.RetryStrategy, completionHandler: @escaping CompletionHandler<Void> - ) -> Cancellable - { + ) -> Cancellable { let requestHandler = AnyRequestHandler { endpoint in var requestBuilder = try self.requestFactory.createRequestBuilder( endpoint: endpoint, @@ -183,18 +185,19 @@ extension REST { return requestBuilder.getRequest() } - let responseHandler = AnyResponseHandler { response, data -> ResponseHandlerResult<Void> in - if HTTPStatus.isSuccess(response.statusCode) { - return .success(()) - } else { - return .unhandledResponse( - try? self.responseDecoder.decode( - ServerErrorResponse.self, - from: data + let responseHandler = + AnyResponseHandler { response, data -> ResponseHandlerResult<Void> in + if HTTPStatus.isSuccess(response.statusCode) { + return .success(()) + } else { + return .unhandledResponse( + try? self.responseDecoder.decode( + ServerErrorResponse.self, + from: data + ) ) - ) + } } - } return addOperation( name: "send-problem-report", @@ -223,7 +226,7 @@ extension REST { var newExpiry: Date { switch self { - case .noTimeAdded(let expiry), .timeAdded(_, let expiry): + case let .noTimeAdded(expiry), let .timeAdded(_, expiry): return expiry } } @@ -232,7 +235,7 @@ extension REST { switch self { case .noTimeAdded: return 0 - case .timeAdded(let timeAdded, _): + case let .timeAdded(timeAdded, _): return TimeInterval(timeAdded) } } @@ -258,5 +261,4 @@ extension REST { let log: String let metadata: [String: String] } - } diff --git a/ios/MullvadVPN/REST/RESTAccessTokenManager.swift b/ios/MullvadVPN/REST/RESTAccessTokenManager.swift index eb124ae829..7d46b16edd 100644 --- a/ios/MullvadVPN/REST/RESTAccessTokenManager.swift +++ b/ios/MullvadVPN/REST/RESTAccessTokenManager.swift @@ -10,7 +10,6 @@ import Foundation import Logging extension REST { - final class AccessTokenManager { private let logger = Logger(label: "REST.AccessTokenManager") private let operationQueue = AsyncOperationQueue() @@ -28,9 +27,9 @@ extension REST { func getAccessToken( accountNumber: String, retryStrategy: REST.RetryStrategy, - completionHandler: @escaping (OperationCompletion<REST.AccessTokenData, REST.Error>) -> Void - ) -> Cancellable - { + completionHandler: @escaping (OperationCompletion<REST.AccessTokenData, REST.Error>) + -> Void + ) -> Cancellable { let operation = ResultBlockOperation<REST.AccessTokenData, REST.Error>( dispatchQueue: dispatchQueue ) { operation in @@ -45,11 +44,14 @@ extension REST { ) { completion in self.dispatchQueue.async { switch completion { - case .success(let tokenData): + case let .success(tokenData): self.tokens[accountNumber] = tokenData - case .failure(let error): - self.logger.error(chainedError: error, message: "Failed to fetch access token.") + case let .failure(error): + self.logger.error( + chainedError: error, + message: "Failed to fetch access token." + ) case .cancelled: break @@ -72,5 +74,4 @@ extension REST { return operation } } - } diff --git a/ios/MullvadVPN/REST/RESTAccountsProxy.swift b/ios/MullvadVPN/REST/RESTAccountsProxy.swift index e9c44f0f63..ace82fafb5 100644 --- a/ios/MullvadVPN/REST/RESTAccountsProxy.swift +++ b/ios/MullvadVPN/REST/RESTAccountsProxy.swift @@ -52,8 +52,7 @@ extension REST { accountNumber: String, retryStrategy: REST.RetryStrategy, completion: @escaping CompletionHandler<AccountData> - ) -> Cancellable - { + ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in var requestBuilder = try self.requestFactory.createRequestBuilder( diff --git a/ios/MullvadVPN/REST/RESTAuthenticationProxy.swift b/ios/MullvadVPN/REST/RESTAuthenticationProxy.swift index a9843ab750..5dda90f543 100644 --- a/ios/MullvadVPN/REST/RESTAuthenticationProxy.swift +++ b/ios/MullvadVPN/REST/RESTAuthenticationProxy.swift @@ -26,8 +26,7 @@ extension REST { accountNumber: String, retryStrategy: REST.RetryStrategy, completion: @escaping CompletionHandler<AccessTokenData> - ) -> Cancellable - { + ) -> Cancellable { let requestHandler = AnyRequestHandler { endpoint in var requestBuilder = try self.requestFactory.createRequestBuilder( endpoint: endpoint, diff --git a/ios/MullvadVPN/REST/RESTAuthorization.swift b/ios/MullvadVPN/REST/RESTAuthorization.swift index 864cdb83a6..e97c42e732 100644 --- a/ios/MullvadVPN/REST/RESTAuthorization.swift +++ b/ios/MullvadVPN/REST/RESTAuthorization.swift @@ -25,7 +25,11 @@ extension REST { private let accountNumber: String private let retryStrategy: REST.RetryStrategy - init(accessTokenManager: AccessTokenManager, accountNumber: String, retryStrategy: REST.RetryStrategy) { + init( + accessTokenManager: AccessTokenManager, + accountNumber: String, + retryStrategy: REST.RetryStrategy + ) { self.accessTokenManager = accessTokenManager self.accountNumber = accountNumber self.retryStrategy = retryStrategy diff --git a/ios/MullvadVPN/REST/RESTDevicesProxy.swift b/ios/MullvadVPN/REST/RESTDevicesProxy.swift index 7ee34c0a8e..871785745b 100644 --- a/ios/MullvadVPN/REST/RESTDevicesProxy.swift +++ b/ios/MullvadVPN/REST/RESTDevicesProxy.swift @@ -7,8 +7,8 @@ // import Foundation -import class WireGuardKitTypes.PublicKey import struct WireGuardKitTypes.IPAddressRange +import class WireGuardKitTypes.PublicKey extension REST { class DevicesProxy: Proxy<AuthProxyConfiguration> { @@ -31,8 +31,7 @@ extension REST { identifier: String, retryStrategy: REST.RetryStrategy, completion: @escaping CompletionHandler<Device> - ) -> Cancellable - { + ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in var path: URLPathTemplate = "devices/{id}" @@ -59,22 +58,23 @@ extension REST { ) ) - let responseHandler = AnyResponseHandler { response, data -> ResponseHandlerResult<Device> in - let httpStatus = HTTPStatus(rawValue: response.statusCode) + let responseHandler = + AnyResponseHandler { response, data -> ResponseHandlerResult<Device> in + let httpStatus = HTTPStatus(rawValue: response.statusCode) - if httpStatus.isSuccess { - return .decoding { - return try self.responseDecoder.decode(Device.self, from: data) - } - } else { - return .unhandledResponse( - try? self.responseDecoder.decode( - ServerErrorResponse.self, - from: data + if httpStatus.isSuccess { + return .decoding { + return try self.responseDecoder.decode(Device.self, from: data) + } + } else { + return .unhandledResponse( + try? self.responseDecoder.decode( + ServerErrorResponse.self, + from: data + ) ) - ) + } } - } return addOperation( name: "get-device", @@ -90,8 +90,7 @@ extension REST { accountNumber: String, retryStrategy: REST.RetryStrategy, completion: @escaping CompletionHandler<[Device]> - ) -> Cancellable - { + ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in var requestBuilder = try self.requestFactory.createRequestBuilder( @@ -132,8 +131,7 @@ extension REST { request: CreateDeviceRequest, retryStrategy: REST.RetryStrategy, completion: @escaping CompletionHandler<Device> - ) -> Cancellable - { + ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in var requestBuilder = try self.requestFactory.createRequestBuilder( @@ -175,8 +173,7 @@ extension REST { identifier: String, retryStrategy: REST.RetryStrategy, completion: @escaping CompletionHandler<Bool> - ) -> Cancellable - { + ) -> Cancellable { let requestHandler = AnyRequestHandler( createURLRequest: { endpoint, authorization in var path: URLPathTemplate = "devices/{id}" @@ -204,25 +201,26 @@ extension REST { ) ) - let responseHandler = AnyResponseHandler { response, data -> ResponseHandlerResult<Bool> in - let statusCode = HTTPStatus(rawValue: response.statusCode) + let responseHandler = + AnyResponseHandler { response, data -> ResponseHandlerResult<Bool> in + let statusCode = HTTPStatus(rawValue: response.statusCode) - switch statusCode { - case let statusCode where statusCode.isSuccess: - return .success(true) + switch statusCode { + case let statusCode where statusCode.isSuccess: + return .success(true) - case .notFound: - return .success(false) + case .notFound: + return .success(false) - default: - return .unhandledResponse( - try? self.responseDecoder.decode( - ServerErrorResponse.self, - from: data + default: + return .unhandledResponse( + try? self.responseDecoder.decode( + ServerErrorResponse.self, + from: data + ) ) - ) + } } - } return addOperation( name: "delete-device", @@ -288,7 +286,6 @@ extension REST { completionHandler: completion ) } - } struct CreateDeviceRequest: Encodable { @@ -341,5 +338,4 @@ extension REST { struct Port: Decodable { let id: String } - } diff --git a/ios/MullvadVPN/REST/RESTError.swift b/ios/MullvadVPN/REST/RESTError.swift index 44db332050..25e51bd1b9 100644 --- a/ios/MullvadVPN/REST/RESTError.swift +++ b/ios/MullvadVPN/REST/RESTError.swift @@ -9,7 +9,6 @@ import Foundation extension REST { - /// An error type returned by REST API classes. enum Error: ChainedError { /// A failure to create URL request. @@ -30,7 +29,7 @@ extension REST { return "Failure to create URL request." case .network: return "Network error." - case .unhandledResponse(let statusCode, let serverResponse): + case let .unhandledResponse(statusCode, serverResponse): var str = "Failure to handle server response: HTTP/\(statusCode)." if let code = serverResponse?.code { @@ -48,7 +47,7 @@ extension REST { } func compareErrorCode(_ code: ServerResponseCode) -> Bool { - if case .unhandledResponse(_, let serverResponse) = self { + if case let .unhandledResponse(_, serverResponse) = self { return serverResponse?.code == code } else { return false @@ -88,5 +87,4 @@ extension REST { self.rawValue = rawValue } } - } diff --git a/ios/MullvadVPN/REST/RESTNetworkOperation.swift b/ios/MullvadVPN/REST/RESTNetworkOperation.swift index 0032d4f904..02b1f5bff2 100644 --- a/ios/MullvadVPN/REST/RESTNetworkOperation.swift +++ b/ios/MullvadVPN/REST/RESTNetworkOperation.swift @@ -36,16 +36,15 @@ extension REST { requestHandler: AnyRequestHandler, responseHandler: AnyResponseHandler<Success>, completionHandler: @escaping CompletionHandler - ) - { - self.urlSession = configuration.session - self.addressCacheStore = configuration.addressCacheStore + ) { + urlSession = configuration.session + addressCacheStore = configuration.addressCacheStore self.retryStrategy = retryStrategy self.requestHandler = requestHandler self.responseHandler = responseHandler var logger = Logger(label: "REST.NetworkOperation") - logger[metadataKey: "name"] = .string(name) + logger[metadataKey: "name"] = .string(name) self.logger = logger super.init( @@ -87,10 +86,10 @@ extension REST { authorizationTask = authorizationProvider.getAuthorization { completion in self.dispatchQueue.async { switch completion { - case .success(let authorization): + case let .success(authorization): self.didReceiveAuthorization(authorization) - case .failure(let error): + case let .failure(error): self.didFailToRequestAuthorization(error) case .cancelled: @@ -108,7 +107,7 @@ extension REST { return } - let endpoint = self.addressCacheStore.getCurrentEndpoint() + let endpoint = addressCacheStore.getCurrentEndpoint() do { let request = try requestHandler.createURLRequest( @@ -136,24 +135,28 @@ extension REST { private func didReceiveURLRequest(_ restRequest: REST.Request, endpoint: AnyIPEndpoint) { dispatchPrecondition(condition: .onQueue(dispatchQueue)) - logger.debug("Send request to \(restRequest.pathTemplate.templateString) via \(endpoint).") + logger + .debug( + "Send request to \(restRequest.pathTemplate.templateString) via \(endpoint)." + ) - networkTask = urlSession.dataTask(with: restRequest.urlRequest) { [weak self] data, response, error in - guard let self = self else { return } + networkTask = urlSession + .dataTask(with: restRequest.urlRequest) { [weak self] data, response, error in + guard let self = self else { return } - self.dispatchQueue.async { - if let error = error { - let urlError = error as! URLError + self.dispatchQueue.async { + if let error = error { + let urlError = error as! URLError - self.didReceiveURLError(urlError, endpoint: endpoint) - } else { - let httpResponse = response as! HTTPURLResponse - let data = data ?? Data() + self.didReceiveURLError(urlError, endpoint: endpoint) + } else { + let httpResponse = response as! HTTPURLResponse + let data = data ?? Data() - self.didReceiveURLResponse(httpResponse, data: data, endpoint: endpoint) + self.didReceiveURLResponse(httpResponse, data: data, endpoint: endpoint) + } } } - } networkTask?.resume() } @@ -225,7 +228,11 @@ extension REST { retryTimer = timer } - private func didReceiveURLResponse(_ response: HTTPURLResponse, data: Data, endpoint: AnyIPEndpoint) { + private func didReceiveURLResponse( + _ response: HTTPURLResponse, + data: Data, + endpoint: AnyIPEndpoint + ) { dispatchPrecondition(condition: .onQueue(dispatchQueue)) logger.debug("Response: \(response.statusCode).") @@ -233,11 +240,11 @@ extension REST { let handlerResult = responseHandler.handleURLResponse(response, data: data) switch handlerResult { - case .success(let output): + case let .success(output): // Response handler produced value. finish(completion: .success(output)) - case .decoding(let decoderBlock): + case let .decoding(decoderBlock): // Response handler returned a block decoding value. let decodeResult = Result { try decoderBlock() } .mapError { error -> REST.Error in @@ -245,11 +252,11 @@ extension REST { } finish(completion: OperationCompletion(result: decodeResult)) - case .unhandledResponse(let serverErrorResponse): + case let .unhandledResponse(serverErrorResponse): // Response handler couldn't handle the response. if serverErrorResponse?.code == .invalidAccessToken, - requiresAuthorization, - retryInvalidAccessTokenError + requiresAuthorization, + retryInvalidAccessTokenError { logger.debug("Received invalid access token error. Retry once.") retryInvalidAccessTokenError = false @@ -264,5 +271,4 @@ extension REST { } } } - } diff --git a/ios/MullvadVPN/REST/RESTProxy.swift b/ios/MullvadVPN/REST/RESTProxy.swift index 5ef15bff86..1030133354 100644 --- a/ios/MullvadVPN/REST/RESTProxy.swift +++ b/ios/MullvadVPN/REST/RESTProxy.swift @@ -32,8 +32,7 @@ extension REST { configuration: ConfigurationType, requestFactory: REST.RequestFactory, responseDecoder: JSONDecoder - ) - { + ) { dispatchQueue = DispatchQueue(label: "REST.\(name).dispatchQueue") operationQueue.name = "REST.\(name).operationQueue" @@ -48,8 +47,7 @@ extension REST { requestHandler: REST.AnyRequestHandler, responseHandler: REST.AnyResponseHandler<Success>, completionHandler: @escaping NetworkOperation<Success>.CompletionHandler - ) -> Cancellable - { + ) -> Cancellable { let operation = NetworkOperation( name: getTaskIdentifier(name: name), dispatchQueue: dispatchQueue, diff --git a/ios/MullvadVPN/REST/RESTRequestFactory.swift b/ios/MullvadVPN/REST/RESTRequestFactory.swift index 2046f26daa..15bccb1930 100644 --- a/ios/MullvadVPN/REST/RESTRequestFactory.swift +++ b/ios/MullvadVPN/REST/RESTRequestFactory.swift @@ -15,7 +15,10 @@ extension REST { let networkTimeout: TimeInterval let bodyEncoder: JSONEncoder - class func withDefaultAPICredentials(pathPrefix: String, bodyEncoder: JSONEncoder) -> RequestFactory { + class func withDefaultAPICredentials( + pathPrefix: String, + bodyEncoder: JSONEncoder + ) -> RequestFactory { return RequestFactory( hostname: ApplicationConfiguration.defaultAPIHostname, pathPrefix: pathPrefix, @@ -29,15 +32,18 @@ extension REST { pathPrefix: String, networkTimeout: TimeInterval, bodyEncoder: JSONEncoder - ) - { + ) { self.hostname = hostname self.pathPrefix = pathPrefix self.networkTimeout = networkTimeout self.bodyEncoder = bodyEncoder } - func createRequest(endpoint: AnyIPEndpoint, method: HTTPMethod, pathTemplate: URLPathTemplate) throws -> REST.Request { + func createRequest( + endpoint: AnyIPEndpoint, + method: HTTPMethod, + pathTemplate: URLPathTemplate + ) throws -> REST.Request { var urlComponents = URLComponents() urlComponents.scheme = "https" urlComponents.path = pathPrefix @@ -108,10 +114,10 @@ extension REST { mutating func setAuthorization(_ authorization: REST.Authorization) { let value: String switch authorization { - case .accountNumber(let accountNumber): + case let .accountNumber(accountNumber): value = "Token \(accountNumber)" - case .accessToken(let accessToken): + case let .accessToken(accessToken): value = "Bearer \(accessToken)" } @@ -138,7 +144,7 @@ extension REST { var errorDescription: String? { switch self { - case .noReplacement(let placeholder): + case let .noReplacement(placeholder): return "Replacement is not provided for \(placeholder)." case .percentEncoding: @@ -154,7 +160,7 @@ extension REST { let slashCharset = CharacterSet(charactersIn: "/") components = value.split(separator: "/").map { subpath -> Component in - if subpath.hasPrefix("{") && subpath.hasSuffix("}") { + if subpath.hasPrefix("{"), subpath.hasSuffix("}") { let name = String(subpath.dropFirst().dropLast()) return .placeholder(name) @@ -193,9 +199,9 @@ extension REST { combinedString += "/" switch component { - case .literal(let string): + case let .literal(string): combinedString += string - case .placeholder(let name): + case let .placeholder(name): combinedString += "{\(name)}" } } @@ -210,10 +216,10 @@ extension REST { combinedPath += "/" switch component { - case .literal(let string): + case let .literal(string): combinedPath += string - case .placeholder(let name): + case let .placeholder(name): if let string = replacements[name] { combinedPath += string } else { @@ -229,5 +235,4 @@ extension REST { return URLPathTemplate(components: lhs.components + rhs.components) } } - } diff --git a/ios/MullvadVPN/REST/RESTResponseHandler.swift b/ios/MullvadVPN/REST/RESTResponseHandler.swift index 1ec56541fa..a1262a2d5c 100644 --- a/ios/MullvadVPN/REST/RESTResponseHandler.swift +++ b/ios/MullvadVPN/REST/RESTResponseHandler.swift @@ -11,7 +11,8 @@ import Foundation protocol RESTResponseHandler { associatedtype Success - func handleURLResponse(_ response: HTTPURLResponse, data: Data) -> REST.ResponseHandlerResult<Success> + func handleURLResponse(_ response: HTTPURLResponse, data: Data) -> REST + .ResponseHandlerResult<Success> } extension REST { @@ -37,7 +38,9 @@ extension REST { handlerBlock = block } - func handleURLResponse(_ response: HTTPURLResponse, data: Data) -> REST.ResponseHandlerResult<Success> { + func handleURLResponse(_ response: HTTPURLResponse, data: Data) -> REST + .ResponseHandlerResult<Success> + { return handlerBlock(response, data) } } diff --git a/ios/MullvadVPN/REST/SSLPinningURLSessionDelegate.swift b/ios/MullvadVPN/REST/SSLPinningURLSessionDelegate.swift index 50b0176288..e872d69937 100644 --- a/ios/MullvadVPN/REST/SSLPinningURLSessionDelegate.swift +++ b/ios/MullvadVPN/REST/SSLPinningURLSessionDelegate.swift @@ -7,11 +7,10 @@ // import Foundation -import Security import Logging +import Security class SSLPinningURLSessionDelegate: NSObject, URLSessionDelegate { - private let sslHostname: String private let trustedRootCertificates: [SecCertificate] @@ -24,10 +23,15 @@ class SSLPinningURLSessionDelegate: NSObject, URLSessionDelegate { // MARK: - URLSessionDelegate - func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + func urlSession( + _ session: URLSession, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void + ) { if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, let serverTrust = challenge.protectionSpace.serverTrust, - verifyServerTrust(serverTrust) { + verifyServerTrust(serverTrust) + { completionHandler(.useCredential, URLCredential(trust: serverTrust)) } else { completionHandler(.rejectProtectionSpace, nil) @@ -43,21 +47,27 @@ class SSLPinningURLSessionDelegate: NSObject, URLSessionDelegate { let sslPolicy = SecPolicyCreateSSL(true, sslHostname as CFString) secResult = SecTrustSetPolicies(serverTrust, sslPolicy) guard secResult == errSecSuccess else { - logger.error("SecTrustSetPolicies failure: \(self.formatErrorMessage(code: secResult))") + logger.error("SecTrustSetPolicies failure: \(formatErrorMessage(code: secResult))") return false } // Set trusted root certificates secResult = SecTrustSetAnchorCertificates(serverTrust, trustedRootCertificates as CFArray) guard secResult == errSecSuccess else { - logger.error("SecTrustSetAnchorCertificates failure: \(self.formatErrorMessage(code: secResult))") + logger + .error( + "SecTrustSetAnchorCertificates failure: \(formatErrorMessage(code: secResult))" + ) return false } // Tell security framework to only trust the provided root certificates secResult = SecTrustSetAnchorCertificatesOnly(serverTrust, true) guard secResult == errSecSuccess else { - logger.error("SecTrustSetAnchorCertificatesOnly failure: \(self.formatErrorMessage(code: secResult))") + logger + .error( + "SecTrustSetAnchorCertificatesOnly failure: \(formatErrorMessage(code: secResult))" + ) return false } @@ -65,7 +75,10 @@ class SSLPinningURLSessionDelegate: NSObject, URLSessionDelegate { if SecTrustEvaluateWithError(serverTrust, &error) { return true } else { - logger.error("SecTrustEvaluateWithError failure: \(error?.localizedDescription ?? "<nil>")") + logger + .error( + "SecTrustEvaluateWithError failure: \(error?.localizedDescription ?? "<nil>")" + ) return false } } diff --git a/ios/MullvadVPN/RelayCache/CachedRelays.swift b/ios/MullvadVPN/RelayCache/CachedRelays.swift index a4cbcc93e5..9675f60c5c 100644 --- a/ios/MullvadVPN/RelayCache/CachedRelays.swift +++ b/ios/MullvadVPN/RelayCache/CachedRelays.swift @@ -9,7 +9,6 @@ import Foundation extension RelayCache { - /// A struct that represents the relay cache on disk struct CachedRelays: Codable { /// E-tag returned by server @@ -21,5 +20,4 @@ extension RelayCache { /// The date when this cache was last updated var updatedAt: Date } - } diff --git a/ios/MullvadVPN/RelayCache/RelayCacheIO.swift b/ios/MullvadVPN/RelayCache/RelayCacheIO.swift index 6a2a4deb13..fe23e02186 100644 --- a/ios/MullvadVPN/RelayCache/RelayCacheIO.swift +++ b/ios/MullvadVPN/RelayCache/RelayCacheIO.swift @@ -86,7 +86,7 @@ extension RelayCache.IO { /// Safely write the cache file on disk using file coordinator. static func write(cacheFileURL: URL, record: RelayCache.CachedRelays) throws { - var result: Result<(), Error>? + var result: Result<Void, Error>? let fileCoordinator = NSFileCoordinator(filePresenter: nil) let accessor = { (fileURLForWriting: URL) in diff --git a/ios/MullvadVPN/RelayCache/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCache/RelayCacheTracker.swift index 702e5b9273..ec56e2b5bb 100644 --- a/ios/MullvadVPN/RelayCache/RelayCacheTracker.swift +++ b/ios/MullvadVPN/RelayCache/RelayCacheTracker.swift @@ -11,7 +11,6 @@ import Logging import UIKit extension RelayCache { - /// Type describing the result of an attempt to fetch the new relay list from server. enum FetchResult: CustomStringConvertible { /// Request to update relays was throttled. @@ -81,7 +80,11 @@ extension RelayCache { /// A shared instance of `RelayCache` static let shared: RelayCache.Tracker = { - let cacheFileURL = RelayCache.IO.defaultCacheFileURL(forSecurityApplicationGroupIdentifier: ApplicationConfiguration.securityGroupIdentifier)! + let cacheFileURL = RelayCache.IO + .defaultCacheFileURL( + forSecurityApplicationGroupIdentifier: ApplicationConfiguration + .securityGroupIdentifier + )! let prebundledRelaysFileURL = RelayCache.IO.preBundledRelaysFileURL! return Tracker( @@ -142,8 +145,7 @@ extension RelayCache { completionHandler: ( (OperationCompletion<RelayCache.FetchResult, Error>) -> Void )? = nil - ) -> Cancellable - { + ) -> Cancellable { let operation = ResultBlockOperation<RelayCache.FetchResult, Error>( dispatchQueue: nil ) { operation in @@ -224,11 +226,10 @@ extension RelayCache { private func handleResponse( completion: OperationCompletion<REST.ServerRelaysCacheResponse, REST.Error> - ) -> OperationCompletion<FetchResult, Error> - { + ) -> OperationCompletion<FetchResult, Error> { let mappedCompletion = completion.tryMap { response -> FetchResult in switch response { - case .newContent(let etag, let relays): + case let .newContent(etag, relays): try self.storeResponse(etag: etag, relays: relays) return .newContent @@ -275,7 +276,6 @@ extension RelayCache { } } - private func scheduleRepeatingTimer(startTime: DispatchWallTime) { let timerSource = DispatchSource.makeTimerSource() timerSource.setEventHandler { [weak self] in @@ -291,5 +291,4 @@ extension RelayCache { self.timerSource = timerSource } } - } diff --git a/ios/MullvadVPN/RelayConstraints.swift b/ios/MullvadVPN/RelayConstraints.swift index 7bedd611a1..e6637f4fd3 100644 --- a/ios/MullvadVPN/RelayConstraints.swift +++ b/ios/MullvadVPN/RelayConstraints.swift @@ -15,7 +15,7 @@ enum RelayConstraint<T>: Codable, Equatable where T: Codable & Equatable { case only(T) var value: T? { - if case .only(let value) = self { + if case let .only(value) = self { return value } else { return nil @@ -45,7 +45,7 @@ enum RelayConstraint<T>: Codable, Equatable where T: Codable & Equatable { switch self { case .any: try container.encode(kRelayConstraintAnyRepr) - case .only(let inner): + case let .only(inner): try container.encode(OnlyRepr(only: inner)) } } @@ -57,7 +57,7 @@ extension RelayConstraint: CustomDebugStringConvertible { switch self { case .any: output += "any" - case .only(let value): + case let .only(value): output += "only(\(String(reflecting: value)))" } return output @@ -99,7 +99,8 @@ enum RelayLocation: Codable, Hashable { default: throw DecodingError.dataCorruptedError( in: container, - debugDescription: "Invalid enum representation") + debugDescription: "Invalid enum representation" + ) } } @@ -107,13 +108,13 @@ enum RelayLocation: Codable, Hashable { var container = encoder.singleValueContainer() switch self { - case .country(let code): + case let .country(code): try container.encode([code]) - case .city(let countryCode, let cityCode): + case let .city(countryCode, cityCode): try container.encode([countryCode, cityCode]) - case .hostname(let countryCode, let cityCode, let hostname): + case let .hostname(countryCode, cityCode, hostname): try container.encode([countryCode, cityCode, hostname]) } } @@ -121,17 +122,16 @@ enum RelayLocation: Codable, Hashable { /// A list of `RelayLocation` items preceding the given one in the relay tree var ascendants: [RelayLocation] { switch self { - case .hostname(let country, let city, _): + case let .hostname(country, city, _): return [.country(country), .city(country, city)] - case .city(let country, _): + case let .city(country, _): return [.country(country)] case .country: return [] } } - } extension RelayLocation: CustomDebugStringConvertible { @@ -139,13 +139,13 @@ extension RelayLocation: CustomDebugStringConvertible { var output = "RelayLocation." switch self { - case .country(let country): + case let .country(country): output += "country(\(String(reflecting: country)))" - case .city(let country, let city): + case let .city(country, city): output += "city(\(String(reflecting: country)), \(String(reflecting: city)))" - case .hostname(let country, let city, let host): + case let .hostname(country, city, host): output += "hostname(\(String(reflecting: country)), " + "\(String(reflecting: city)), " + "\(String(reflecting: host)))" @@ -156,11 +156,11 @@ extension RelayLocation: CustomDebugStringConvertible { var stringRepresentation: String { switch self { - case .country(let country): + case let .country(country): return country - case .city(let country, let city): + case let .city(country, city): return "\(country)-\(city)" - case .hostname(let country, let city, let host): + case let .hostname(country, city, host): return "\(country)-\(city)-\(host)" } } diff --git a/ios/MullvadVPN/RelaySelector.swift b/ios/MullvadVPN/RelaySelector.swift index c29c8ccef5..2b2a269c41 100644 --- a/ios/MullvadVPN/RelaySelector.swift +++ b/ios/MullvadVPN/RelaySelector.swift @@ -7,8 +7,8 @@ // import Foundation -import Network import Logging +import Network struct RelaySelectorResult: Codable { var endpoint: MullvadEndpoint @@ -41,12 +41,15 @@ struct NoRelaysSatisfyingConstraintsError: LocalizedError { enum RelaySelector {} extension RelaySelector { - - static func evaluate(relays: REST.ServerRelaysResponse, constraints: RelayConstraints) throws -> RelaySelectorResult { + static func evaluate( + relays: REST.ServerRelaysResponse, + constraints: RelayConstraints + ) throws -> RelaySelectorResult { let filteredRelays = applyConstraints(constraints, relays: Self.parseRelaysResponse(relays)) guard let relayWithLocation = pickRandomRelay(relays: filteredRelays), - let port = pickRandomPort(rawPortRanges: relays.wireguard.portRanges) else { + let port = pickRandomPort(rawPortRanges: relays.wireguard.portRanges) + else { throw NoRelaysSatisfyingConstraintsError() } @@ -69,28 +72,31 @@ extension RelaySelector { } /// Produce a list of `RelayWithLocation` items satisfying the given constraints - private static func applyConstraints(_ constraints: RelayConstraints, relays: [RelayWithLocation]) -> [RelayWithLocation] { - return relays.filter { (relayWithLocation) -> Bool in + private static func applyConstraints( + _ constraints: RelayConstraints, + relays: [RelayWithLocation] + ) -> [RelayWithLocation] { + return relays.filter { relayWithLocation -> Bool in switch constraints.location { case .any: return true - case .only(let relayConstraint): + case let .only(relayConstraint): switch relayConstraint { - case .country(let countryCode): + case let .country(countryCode): return relayWithLocation.location.countryCode == countryCode && relayWithLocation.relay.includeInCountry - case .city(let countryCode, let cityCode): + case let .city(countryCode, cityCode): return relayWithLocation.location.countryCode == countryCode && relayWithLocation.location.cityCode == cityCode - case .hostname(let countryCode, let cityCode, let hostname): + case let .hostname(countryCode, cityCode, hostname): return relayWithLocation.location.countryCode == countryCode && relayWithLocation.location.cityCode == cityCode && relayWithLocation.relay.hostname == hostname } } - }.filter { (relayWithLocation) -> Bool in + }.filter { relayWithLocation -> Bool in return relayWithLocation.relay.active } } @@ -107,10 +113,11 @@ extension RelaySelector { // Pick a random number in the range 1 - totalWeight. This choses the relay with a // non-zero weight. - var i = (1...totalWeight).randomElement()! + var i = (1 ... totalWeight).randomElement()! - let randomRelay = relays.first { (relayWithLocation) -> Bool in - let (result, isOverflow) = i.subtractingReportingOverflow(relayWithLocation.relay.weight) + let randomRelay = relays.first { relayWithLocation -> Bool in + let (result, isOverflow) = i + .subtractingReportingOverflow(relayWithLocation.relay.weight) i = isOverflow ? 0 : result @@ -128,7 +135,7 @@ extension RelaySelector { return partialResult + closedRange.count } - guard var portIndex = (0..<portAmount).randomElement() else { + guard var portIndex = (0 ..< portAmount).randomElement() else { return nil } @@ -154,15 +161,18 @@ extension RelaySelector { let endPort = inputRange[1] if startPort <= endPort { - return (startPort...endPort) + return (startPort ... endPort) } else { return nil } } } - private static func parseRelaysResponse(_ response: REST.ServerRelaysResponse) -> [RelayWithLocation] { - return response.wireguard.relays.compactMap { (serverRelay) -> RelayWithLocation? in + private static func parseRelaysResponse( + _ response: REST + .ServerRelaysResponse + ) -> [RelayWithLocation] { + return response.wireguard.relays.compactMap { serverRelay -> RelayWithLocation? in guard let serverLocation = response.locations[serverRelay.location] else { return nil } let locationComponents = serverRelay.location.split(separator: "-") @@ -180,5 +190,4 @@ extension RelaySelector { return RelayWithLocation(relay: serverRelay, location: location) } } - } diff --git a/ios/MullvadVPN/Result+Extensions.swift b/ios/MullvadVPN/Result+Extensions.swift index 9a2245d673..0ea447752e 100644 --- a/ios/MullvadVPN/Result+Extensions.swift +++ b/ios/MullvadVPN/Result+Extensions.swift @@ -11,7 +11,7 @@ import Foundation extension Result { var value: Success? { switch self { - case .success(let value): + case let .success(value): return value case .failure: return nil @@ -22,16 +22,16 @@ extension Result { switch self { case .success: return nil - case .failure(let error): + case let .failure(error): return error } } } extension Result { - func flattenValue<T>() -> T? where Success == Optional<T> { + func flattenValue<T>() -> T? where Success == T? { switch self { - case .success(let optional): + case let .success(optional): return optional.flatMap { $0 } case .failure: return nil diff --git a/ios/MullvadVPN/Result+UIBackgroundFetchResult.swift b/ios/MullvadVPN/Result+UIBackgroundFetchResult.swift index 6cebcaaa75..e9bcf01c62 100644 --- a/ios/MullvadVPN/Result+UIBackgroundFetchResult.swift +++ b/ios/MullvadVPN/Result+UIBackgroundFetchResult.swift @@ -11,7 +11,7 @@ import UIKit extension OperationCompletion { func backgroundFetchResult(_ hasNewData: (Success) -> Bool) -> UIBackgroundFetchResult { switch self { - case .success(let value): + case let .success(value): return hasNewData(value) ? .newData : .noData case .cancelled: return .noData @@ -31,7 +31,7 @@ extension UIBackgroundFetchResult: CustomStringConvertible { case .failed: return "failed" @unknown default: - return "unknown (rawValue: \(self.rawValue)" + return "unknown (rawValue: \(rawValue)" } } diff --git a/ios/MullvadVPN/RevokedDeviceViewController.swift b/ios/MullvadVPN/RevokedDeviceViewController.swift index 117ad69e91..f4c02367ca 100644 --- a/ios/MullvadVPN/RevokedDeviceViewController.swift +++ b/ios/MullvadVPN/RevokedDeviceViewController.swift @@ -13,9 +13,8 @@ protocol RevokedDeviceViewControllerDelegate: AnyObject { } class RevokedDeviceViewController: UIViewController, RootContainment, TunnelObserver { - private lazy var imageView: StatusImageView = { - let statusImageView = StatusImageView(style: .failure) + let statusImageView = StatusImageView(style: .failure) statusImageView.translatesAutoresizingMaskIntoConstraints = false return statusImageView }() @@ -147,7 +146,7 @@ class RevokedDeviceViewController: UIViewController, RootContainment, TunnelObse logoutButton.topAnchor.constraint(greaterThanOrEqualTo: footerLabel.bottomAnchor), logoutButton.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), logoutButton.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), - logoutButton.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor) + logoutButton.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor), ]) TunnelManager.shared.addObserver(self) @@ -180,12 +179,14 @@ class RevokedDeviceViewController: UIViewController, RootContainment, TunnelObse // no-op } - func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2) { + func tunnelManager( + _ manager: TunnelManager, + didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2 + ) { // no-op } func tunnelManager(_ manager: TunnelManager, didFailWithError error: Error) { // no-op } - } diff --git a/ios/MullvadVPN/RootContainerViewController.swift b/ios/MullvadVPN/RootContainerViewController.swift index 35d48ef157..573f4c5c7f 100644 --- a/ios/MullvadVPN/RootContainerViewController.swift +++ b/ios/MullvadVPN/RootContainerViewController.swift @@ -36,26 +36,29 @@ struct HeaderBarPresentation { /// A protocol that defines the relationship between the root container and its child controllers protocol RootContainment { - /// Return the preferred header bar style var preferredHeaderBarPresentation: HeaderBarPresentation { get } /// Return true if the view controller prefers header bar hidden var prefersHeaderBarHidden: Bool { get } - } protocol RootContainerViewControllerDelegate: AnyObject { - func rootContainerViewControllerShouldShowSettings(_ controller: RootContainerViewController, navigateTo route: SettingsNavigationRoute?, animated: Bool) + func rootContainerViewControllerShouldShowSettings( + _ controller: RootContainerViewController, + navigateTo route: SettingsNavigationRoute?, + animated: Bool + ) - func rootContainerViewSupportedInterfaceOrientations(_ controller: RootContainerViewController) -> UIInterfaceOrientationMask + func rootContainerViewSupportedInterfaceOrientations(_ controller: RootContainerViewController) + -> UIInterfaceOrientationMask - func rootContainerViewAccessibilityPerformMagicTap(_ controller: RootContainerViewController) -> Bool + func rootContainerViewAccessibilityPerformMagicTap(_ controller: RootContainerViewController) + -> Bool } /// A root container view controller class RootContainerViewController: UIViewController { - typealias CompletionHandler = () -> Void private let headerBarView = HeaderBarView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) @@ -91,7 +94,8 @@ class RootContainerViewController: UIViewController { } override var disablesAutomaticKeyboardDismissal: Bool { - return topViewController?.disablesAutomaticKeyboardDismissal ?? super.disablesAutomaticKeyboardDismissal + return topViewController?.disablesAutomaticKeyboardDismissal ?? super + .disablesAutomaticKeyboardDismissal } // MARK: - View lifecycle @@ -165,10 +169,11 @@ class RootContainerViewController: UIViewController { // MARK: - Public - func setViewControllers(_ newViewControllers: [UIViewController], - animated: Bool, - completion: CompletionHandler? = nil) - { + func setViewControllers( + _ newViewControllers: [UIViewController], + animated: Bool, + completion: CompletionHandler? = nil + ) { // Fetch the initial orientation mask if interfaceOrientationMask == nil { updateInterfaceOrientation(attemptRotateToDeviceOrientation: false) @@ -182,11 +187,20 @@ class RootContainerViewController: UIViewController { ) } - func pushViewController(_ viewController: UIViewController, animated: Bool, completion: CompletionHandler? = nil) { - var newViewControllers = viewControllers.filter({ $0 != viewController }) + func pushViewController( + _ viewController: UIViewController, + animated: Bool, + completion: CompletionHandler? = nil + ) { + var newViewControllers = viewControllers.filter { $0 != viewController } newViewControllers.append(viewController) - setViewControllersInternal(newViewControllers, isUnwinding: false, animated: animated, completion: completion) + setViewControllersInternal( + newViewControllers, + isUnwinding: false, + animated: animated, + completion: completion + ) } func popViewController(animated: Bool, completion: CompletionHandler? = nil) { @@ -195,12 +209,22 @@ class RootContainerViewController: UIViewController { var newViewControllers = viewControllers newViewControllers.removeLast() - setViewControllersInternal(newViewControllers, isUnwinding: true, animated: animated, completion: completion) + setViewControllersInternal( + newViewControllers, + isUnwinding: true, + animated: animated, + completion: completion + ) } func popToRootViewController(animated: Bool, completion: CompletionHandler? = nil) { - if let rootController = self.viewControllers.first, self.viewControllers.count > 1 { - setViewControllersInternal([rootController], isUnwinding: true, animated: animated, completion: completion) + if let rootController = viewControllers.first, viewControllers.count > 1 { + setViewControllersInternal( + [rootController], + isUnwinding: true, + animated: animated, + completion: completion + ) } } @@ -215,7 +239,11 @@ class RootContainerViewController: UIViewController { /// Request to display settings controller func showSettings(navigateTo route: SettingsNavigationRoute? = nil, animated: Bool) { - delegate?.rootContainerViewControllerShouldShowSettings(self, navigateTo: route, animated: animated) + delegate?.rootContainerViewControllerShouldShowSettings( + self, + navigateTo: route, + animated: animated + ) } /// Enable or disable the settings bar button displayed in the header bar @@ -235,7 +263,11 @@ class RootContainerViewController: UIViewController { } else { settingsButton = HeaderBarView.makeSettingsButton() settingsButton.isEnabled = headerBarView.settingsButton.isEnabled - settingsButton.addTarget(self, action: #selector(handleSettingsButtonTap), for: .touchUpInside) + settingsButton.addTarget( + self, + action: #selector(handleSettingsButtonTap), + for: .touchUpInside + ) presentationContainerSettingsButton = settingsButton } @@ -246,8 +278,10 @@ class RootContainerViewController: UIViewController { presentationContainer.addSubview(settingsButton) NSLayoutConstraint.activate([ - settingsButton.centerXAnchor.constraint(equalTo: headerBarView.settingsButton.centerXAnchor), - settingsButton.centerYAnchor.constraint(equalTo: headerBarView.settingsButton.centerYAnchor), + settingsButton.centerXAnchor + .constraint(equalTo: headerBarView.settingsButton.centerXAnchor), + settingsButton.centerYAnchor + .constraint(equalTo: headerBarView.settingsButton.centerYAnchor), ]) } @@ -269,7 +303,8 @@ class RootContainerViewController: UIViewController { // MARK: - Accessibility override func accessibilityPerformMagicTap() -> Bool { - return delegate?.rootContainerViewAccessibilityPerformMagicTap(self) ?? super.accessibilityPerformMagicTap() + return delegate?.rootContainerViewAccessibilityPerformMagicTap(self) ?? super + .accessibilityPerformMagicTap() } // MARK: - Private @@ -279,7 +314,7 @@ class RootContainerViewController: UIViewController { transitionContainer.topAnchor.constraint(equalTo: view.topAnchor), transitionContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor), transitionContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor), - transitionContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor) + transitionContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor), ] transitionContainer.translatesAutoresizingMaskIntoConstraints = false @@ -292,7 +327,7 @@ class RootContainerViewController: UIViewController { let constraints = [ headerBarView.topAnchor.constraint(equalTo: view.topAnchor), headerBarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - headerBarView.trailingAnchor.constraint(equalTo: view.trailingAnchor) + headerBarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), ] headerBarView.translatesAutoresizingMaskIntoConstraints = false @@ -300,7 +335,11 @@ class RootContainerViewController: UIViewController { // Prevent automatic layout margins adjustment as we manually control them. headerBarView.insetsLayoutMarginsFromSafeArea = false - headerBarView.settingsButton.addTarget(self, action: #selector(handleSettingsButtonTap), for: .touchUpInside) + headerBarView.settingsButton.addTarget( + self, + action: #selector(handleSettingsButtonTap), + for: .touchUpInside + ) view.addSubview(headerBarView) @@ -311,7 +350,12 @@ class RootContainerViewController: UIViewController { showSettings(animated: true) } - private func setViewControllersInternal(_ newViewControllers: [UIViewController], isUnwinding: Bool, animated: Bool, completion: CompletionHandler? = nil) { + private func setViewControllersInternal( + _ newViewControllers: [UIViewController], + isUnwinding: Bool, + animated: Bool, + completion: CompletionHandler? = nil + ) { // Dot not handle appearance events when the container itself is not visible let shouldHandleAppearanceEvents = view.window != nil @@ -348,7 +392,9 @@ class RootContainerViewController: UIViewController { self.endChildControllerTransition(sourceViewController) } - if let targetViewController = targetViewController, sourceViewController != targetViewController { + if let targetViewController = targetViewController, + sourceViewController != targetViewController + { self.endChildControllerTransition(targetViewController) } } @@ -394,10 +440,20 @@ class RootContainerViewController: UIViewController { // Begin appearance transition if shouldHandleAppearanceEvents { if let sourceViewController = sourceViewController { - beginChildControllerTransition(sourceViewController, isAppearing: false, animated: shouldAnimate) + beginChildControllerTransition( + sourceViewController, + isAppearing: false, + animated: shouldAnimate + ) } - if let targetViewController = targetViewController, sourceViewController != targetViewController { - beginChildControllerTransition(targetViewController, isAppearing: true, animated: shouldAnimate) + if let targetViewController = targetViewController, + sourceViewController != targetViewController + { + beginChildControllerTransition( + targetViewController, + isAppearing: true, + animated: shouldAnimate + ) } setNeedsStatusBarAppearanceUpdate() } @@ -413,12 +469,12 @@ class RootContainerViewController: UIViewController { transition.type = .push // Pick the animation movement direction - let sourceIndex = sourceViewController.flatMap({ newViewControllers.firstIndex(of: $0) }) - let targetIndex = targetViewController.flatMap({ newViewControllers.firstIndex(of: $0) }) + let sourceIndex = sourceViewController.flatMap { newViewControllers.firstIndex(of: $0) } + let targetIndex = targetViewController.flatMap { newViewControllers.firstIndex(of: $0) } switch (sourceIndex, targetIndex) { - case (.some(let lhs), .some(let rhs)): - transition.subtype = lhs > rhs ? .fromLeft : .fromRight + case let (.some(lhs), .some(rhs)): + transition.subtype = lhs > rhs ? .fromLeft : .fromRight case (.none, .some): transition.subtype = isUnwinding ? .fromLeft : .fromRight default: @@ -511,7 +567,8 @@ class RootContainerViewController: UIViewController { } private func updateInterfaceOrientation(attemptRotateToDeviceOrientation: Bool) { - let newSupportedOrientations = delegate?.rootContainerViewSupportedInterfaceOrientations(self) + let newSupportedOrientations = delegate? + .rootContainerViewSupportedInterfaceOrientations(self) if interfaceOrientationMask != newSupportedOrientations { interfaceOrientationMask = newSupportedOrientations @@ -523,7 +580,11 @@ class RootContainerViewController: UIViewController { } } - private func beginChildControllerTransition(_ controller: UIViewController, isAppearing: Bool, animated: Bool) { + private func beginChildControllerTransition( + _ controller: UIViewController, + isAppearing: Bool, + animated: Bool + ) { if appearingController != controller, isAppearing { appearingController = controller controller.beginAppearanceTransition(isAppearing, animated: animated) @@ -554,12 +615,10 @@ class RootContainerViewController: UIViewController { // Tell accessibility that the significant part of screen was changed. UIAccessibility.post(notification: .screenChanged, argument: nil) } - } /// A UIViewController extension that gives view controllers an access to root container extension UIViewController { - var rootContainerController: RootContainerViewController? { var current: UIViewController? = self let iterator = AnyIterator { () -> UIViewController? in @@ -577,5 +636,4 @@ extension UIViewController { func setNeedsHeaderBarHiddenAppearanceUpdate() { rootContainerController?.updateHeaderBarHiddenAppearance() } - } diff --git a/ios/MullvadVPN/SKProduct+Formatting.swift b/ios/MullvadVPN/SKProduct+Formatting.swift index 19063622bc..b5bf461ef6 100644 --- a/ios/MullvadVPN/SKProduct+Formatting.swift +++ b/ios/MullvadVPN/SKProduct+Formatting.swift @@ -10,7 +10,6 @@ import Foundation import StoreKit extension SKProduct { - var localizedPrice: String? { let formatter = NumberFormatter() formatter.locale = priceLocale @@ -18,5 +17,4 @@ extension SKProduct { return formatter.string(from: price) } - } diff --git a/ios/MullvadVPN/SceneDelegate.swift b/ios/MullvadVPN/SceneDelegate.swift index 5fe4dcbc52..003672284d 100644 --- a/ios/MullvadVPN/SceneDelegate.swift +++ b/ios/MullvadVPN/SceneDelegate.swift @@ -6,8 +6,8 @@ // Copyright © 2022 Mullvad VPN AB. All rights reserved. // -import UIKit import Logging +import UIKit class SceneDelegate: UIResponder { private let logger = Logger(label: "SceneDelegate") @@ -177,7 +177,11 @@ extension SceneDelegate: UIWindowSceneDelegate { // MARK: - RootContainerViewControllerDelegate extension SceneDelegate: RootContainerViewControllerDelegate { - func rootContainerViewControllerShouldShowSettings(_ controller: RootContainerViewController, navigateTo route: SettingsNavigationRoute?, animated: Bool) { + func rootContainerViewControllerShouldShowSettings( + _ controller: RootContainerViewController, + navigateTo route: SettingsNavigationRoute?, + animated: Bool + ) { // Check if settings controller is already presented. if let settingsNavController = settingsNavController { if let route = route { @@ -197,11 +201,13 @@ extension SceneDelegate: RootContainerViewControllerDelegate { } // Save the reference for later. - self.settingsNavController = navController + settingsNavController = navController } } - func rootContainerViewSupportedInterfaceOrientations(_ controller: RootContainerViewController) -> UIInterfaceOrientationMask { + func rootContainerViewSupportedInterfaceOrientations(_ controller: RootContainerViewController) + -> UIInterfaceOrientationMask + { switch UIDevice.current.userInterfaceIdiom { case .pad: return [.landscape, .portrait] @@ -212,7 +218,9 @@ extension SceneDelegate: RootContainerViewControllerDelegate { } } - func rootContainerViewAccessibilityPerformMagicTap(_ controller: RootContainerViewController) -> Bool { + func rootContainerViewAccessibilityPerformMagicTap(_ controller: RootContainerViewController) + -> Bool + { guard TunnelManager.shared.deviceState.isLoggedIn else { return false } switch TunnelManager.shared.tunnelStatus.state { @@ -228,7 +236,6 @@ extension SceneDelegate: RootContainerViewControllerDelegate { } extension SceneDelegate { - private func setupPadUI() { let tunnelManager = TunnelManager.shared let selectLocationController = makeSelectLocationController() @@ -237,12 +244,13 @@ extension SceneDelegate { let splitViewController = CustomSplitViewController() splitViewController.delegate = self splitViewController.minimumPrimaryColumnWidth = UIMetrics.minimumSplitViewSidebarWidth - splitViewController.preferredPrimaryColumnWidthFraction = UIMetrics.maximumSplitViewSidebarWidthFraction + splitViewController.preferredPrimaryColumnWidthFraction = UIMetrics + .maximumSplitViewSidebarWidthFraction splitViewController.primaryEdge = .trailing splitViewController.dividerColor = UIColor.MainSplitView.dividerColor splitViewController.viewControllers = [selectLocationController, connectController] - self.selectLocationViewController = selectLocationController + selectLocationViewController = selectLocationController self.splitViewController = splitViewController self.connectController = connectController @@ -264,7 +272,10 @@ extension SceneDelegate { // Dismiss modal root container if needed before proceeding. if self.isModalRootPresented { - self.modalRootContainer.dismiss(animated: animated, completion: didDismissModalRoot) + self.modalRootContainer.dismiss( + animated: animated, + completion: didDismissModalRoot + ) } else { didDismissModalRoot() } @@ -291,7 +302,7 @@ extension SceneDelegate { if TermsOfService.isAgreed { showNextController(false) } else { - let termsOfServiceController = self.makeTermsOfServiceController { _ in + let termsOfServiceController = makeTermsOfServiceController { _ in showNextController(true) } @@ -346,7 +357,7 @@ extension SceneDelegate { if TermsOfService.isAgreed { showNextController(false) } else { - let termsOfServiceController = self.makeTermsOfServiceController { _ in + let termsOfServiceController = makeTermsOfServiceController { _ in showNextController(true) } @@ -354,7 +365,9 @@ extension SceneDelegate { } } - private func makeSettingsNavigationController(route: SettingsNavigationRoute?) -> SettingsNavigationController { + private func makeSettingsNavigationController(route: SettingsNavigationRoute?) + -> SettingsNavigationController + { let navController = SettingsNavigationController() navController.settingsDelegate = self @@ -400,8 +413,7 @@ extension SceneDelegate { private func makeTermsOfServiceController( completion: @escaping (UIViewController) -> Void - ) -> TermsOfServiceViewController - { + ) -> TermsOfServiceViewController { let controller = TermsOfServiceViewController() if UIDevice.current.userInterfaceIdiom == .pad { @@ -432,7 +444,7 @@ extension SceneDelegate { } private func showAccountSettingsControllerIfAccountExpired() { - guard case .loggedIn(let accountData, _) = TunnelManager.shared.deviceState else { + guard case let .loggedIn(accountData, _) = TunnelManager.shared.deviceState else { return } @@ -481,7 +493,6 @@ extension SceneDelegate { // MARK: - LoginViewControllerDelegate extension SceneDelegate: LoginViewControllerDelegate { - func loginViewController( _ controller: LoginViewController, shouldHandleLoginAction action: LoginAction, @@ -495,11 +506,11 @@ extension SceneDelegate: LoginViewControllerDelegate { // RootContainer's settings button will be re-enabled in `loginViewControllerDidFinishLogin` completion(operationCompletion) - case .failure(let error): + case let .failure(error): // Show device management controller when too many devices detected during log in. - if case .useExistingAccount(let accountNumber) = action, - let restError = error as? REST.Error, - restError.compareErrorCode(.maxDevicesReached) + if case let .useExistingAccount(accountNumber) = action, + let restError = error as? REST.Error, + restError.compareErrorCode(.maxDevicesReached) { self.lastLoginAction = action @@ -508,17 +519,18 @@ extension SceneDelegate: LoginViewControllerDelegate { ) deviceController.delegate = self - deviceController.fetchDevices(animateUpdates: false) { [weak self] operationCompletion in - controller.rootContainerController?.pushViewController( - deviceController, - animated: true - ) + deviceController + .fetchDevices(animateUpdates: false) { [weak self] operationCompletion in + controller.rootContainerController?.pushViewController( + deviceController, + animated: true + ) - // Return .cancelled to login controller upon success. - completion(operationCompletion.flatMap { .cancelled }) + // Return .cancelled to login controller upon success. + completion(operationCompletion.flatMap { .cancelled }) - self?.setEnableSettingsButton(isEnabled: true, from: controller) - } + self?.setEnableSettingsButton(isEnabled: true, from: controller) + } } else { fallthrough } @@ -527,12 +539,11 @@ extension SceneDelegate: LoginViewControllerDelegate { self.setEnableSettingsButton(isEnabled: true, from: controller) completion(operationCompletion) } - } } func loginViewControllerDidFinishLogin(_ controller: LoginViewController) { - self.lastLoginAction = nil + lastLoginAction = nil // Move the settings button back into header bar rootContainer.removeSettingsButtonFromPresentationContainer() @@ -547,13 +558,13 @@ extension SceneDelegate: LoginViewControllerDelegate { switch UIDevice.current.userInterfaceIdiom { case .phone: - let connectController = self.makeConnectViewController() + let connectController = makeConnectViewController() rootContainer.pushViewController(connectController, animated: true) { self.showAccountSettingsControllerIfAccountExpired() } self.connectController = connectController case .pad: - self.showSplitViewMaster(true, animated: true) + showSplitViewMaster(true, animated: true) controller.dismiss(animated: true) { self.showAccountSettingsControllerIfAccountExpired() @@ -570,7 +581,6 @@ extension SceneDelegate: LoginViewControllerDelegate { container.setEnableSettingsButton(isEnabled) } } - } // MARK: - DeviceManagementViewControllerDelegate @@ -582,7 +592,8 @@ extension SceneDelegate: DeviceManagementViewControllerDelegate { func deviceManagementViewControllerDidFinish(_ controller: DeviceManagementViewController) { let currentRootContainer = controller.rootContainerController - let loginViewController = currentRootContainer?.viewControllers.first as? LoginViewController + let loginViewController = currentRootContainer?.viewControllers + .first as? LoginViewController currentRootContainer?.popViewController(animated: true) { if let lastLoginAction = self.lastLoginAction { @@ -595,25 +606,29 @@ extension SceneDelegate: DeviceManagementViewControllerDelegate { // MARK: - SettingsNavigationControllerDelegate extension SceneDelegate: SettingsNavigationControllerDelegate { - - func settingsNavigationController(_ controller: SettingsNavigationController, didFinishWithReason reason: SettingsDismissReason) { + func settingsNavigationController( + _ controller: SettingsNavigationController, + didFinishWithReason reason: SettingsDismissReason + ) { if case .userLoggedOut = reason { showLoginViewAfterLogout(dismissController: controller) } else { controller.dismiss(animated: true) } } - } // MARK: - ConnectViewControllerDelegate extension SceneDelegate: ConnectViewControllerDelegate { - func connectViewControllerShouldShowSelectLocationPicker(_ controller: ConnectViewController) { let contentController = makeSelectLocationController() contentController.navigationItem.largeTitleDisplayMode = .never - contentController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDismissSelectLocationController(_:))) + contentController.navigationItem.rightBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .done, + target: self, + action: #selector(handleDismissSelectLocationController(_:)) + ) let navController = SelectLocationNavigationController(contentController: contentController) rootContainer.present(navController, animated: true) @@ -629,7 +644,10 @@ extension SceneDelegate: ConnectViewControllerDelegate { // MARK: - NotificationManagerDelegate extension SceneDelegate: NotificationManagerDelegate { - func notificationManagerDidUpdateInAppNotifications(_ manager: NotificationManager, notifications: [InAppNotificationDescriptor]) { + func notificationManagerDidUpdateInAppNotifications( + _ manager: NotificationManager, + notifications: [InAppNotificationDescriptor] + ) { connectController?.notificationController.setNotifications(notifications, animated: true) } } @@ -637,10 +655,13 @@ extension SceneDelegate: NotificationManagerDelegate { // MARK: - SelectLocationViewControllerDelegate extension SceneDelegate: SelectLocationViewControllerDelegate { - func selectLocationViewController(_ controller: SelectLocationViewController, didSelectRelayLocation relayLocation: RelayLocation) { + func selectLocationViewController( + _ controller: SelectLocationViewController, + didSelectRelayLocation relayLocation: RelayLocation + ) { // Dismiss view controller in modal presentation if controller.presentingViewController != nil { - self.window?.isUserInteractionEnabled = false + window?.isUserInteractionEnabled = false DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250)) { self.window?.isUserInteractionEnabled = true controller.dismiss(animated: true) { @@ -674,8 +695,10 @@ extension SceneDelegate: RevokedDeviceViewControllerDelegate { // MARK: - UIAdaptivePresentationControllerDelegate extension SceneDelegate: UIAdaptivePresentationControllerDelegate { - - func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { + func adaptivePresentationStyle( + for controller: UIPresentationController, + traitCollection: UITraitCollection + ) -> UIModalPresentationStyle { if controller.presentedViewController is RootContainerViewController { return traitCollection.horizontalSizeClass == .regular ? .formSheet : .fullScreen } else { @@ -683,7 +706,11 @@ extension SceneDelegate: UIAdaptivePresentationControllerDelegate { } } - func presentationController(_ presentationController: UIPresentationController, willPresentWithAdaptiveStyle style: UIModalPresentationStyle, transitionCoordinator: UIViewControllerTransitionCoordinator?) { + func presentationController( + _ presentationController: UIPresentationController, + willPresentWithAdaptiveStyle style: UIModalPresentationStyle, + transitionCoordinator: UIViewControllerTransitionCoordinator? + ) { let actualStyle: UIModalPresentationStyle // When adaptive presentation is not changing, the `style` is set to `.none` @@ -694,7 +721,9 @@ extension SceneDelegate: UIAdaptivePresentationControllerDelegate { } // Force hide header bar in .formSheet presentation and show it in .fullScreen presentation - if let wrapper = presentationController.presentedViewController as? RootContainerViewController { + if let wrapper = presentationController + .presentedViewController as? RootContainerViewController + { wrapper.setOverrideHeaderBarHidden(actualStyle == .formSheet, animated: false) } @@ -734,7 +763,10 @@ extension SceneDelegate: TunnelObserver { // no-op } - func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2) { + func tunnelManager( + _ manager: TunnelManager, + didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2 + ) { // no-op } @@ -743,7 +775,8 @@ extension SceneDelegate: TunnelObserver { switch UIDevice.current.userInterfaceIdiom { case .phone: - guard let loginController = rootContainer.viewControllers.first as? LoginViewController else { + guard let loginController = rootContainer.viewControllers.first as? LoginViewController + else { return } @@ -751,13 +784,15 @@ extension SceneDelegate: TunnelObserver { let viewControllers = [ loginController, - makeRevokedDeviceController() + makeRevokedDeviceController(), ] rootContainer.setViewControllers(viewControllers, animated: true) case .pad: - guard let loginController = modalRootContainer.viewControllers.first as? LoginViewController else { + guard let loginController = modalRootContainer.viewControllers + .first as? LoginViewController + else { return } @@ -765,7 +800,7 @@ extension SceneDelegate: TunnelObserver { let viewControllers = [ loginController, - makeRevokedDeviceController() + makeRevokedDeviceController(), ] let didDismissSettings = { @@ -794,36 +829,41 @@ extension SceneDelegate: TunnelObserver { // MARK: - RelayCacheObserver extension SceneDelegate: RelayCacheObserver { - - func relayCache(_ relayCache: RelayCache.Tracker, didUpdateCachedRelays cachedRelays: RelayCache.CachedRelays) { + func relayCache( + _ relayCache: RelayCache.Tracker, + didUpdateCachedRelays cachedRelays: RelayCache.CachedRelays + ) { selectLocationViewController?.setCachedRelays(cachedRelays) } - } - // MARK: - UISplitViewControllerDelegate extension SceneDelegate: UISplitViewControllerDelegate { - - func primaryViewController(forExpanding splitViewController: UISplitViewController) -> UIViewController? { + func primaryViewController(forExpanding splitViewController: UISplitViewController) + -> UIViewController? + { // Restore the select location controller as primary when expanding the split view return selectLocationViewController } - func primaryViewController(forCollapsing splitViewController: UISplitViewController) -> UIViewController? { + func primaryViewController(forCollapsing splitViewController: UISplitViewController) + -> UIViewController? + { // Set the connect controller as primary when collapsing the split view return connectController } - func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? { + func splitViewController( + _ splitViewController: UISplitViewController, + separateSecondaryFrom primaryViewController: UIViewController + ) -> UIViewController? { // Dismiss the select location controller when expanding the split view - if self.selectLocationViewController?.presentingViewController != nil { - self.selectLocationViewController?.dismiss(animated: false) + if selectLocationViewController?.presentingViewController != nil { + selectLocationViewController?.dismiss(animated: false) } return nil } - } // MARK: - Window factory @@ -837,6 +877,7 @@ struct ClassicWindowFactory: WindowFactory { return UIWindow(frame: UIScreen.main.bounds) } } + @available(iOS 13.0, *) struct SceneWindowFactory: WindowFactory { let windowScene: UIWindowScene diff --git a/ios/MullvadVPN/SelectLocationCell.swift b/ios/MullvadVPN/SelectLocationCell.swift index eb52175cd4..c2050560c7 100644 --- a/ios/MullvadVPN/SelectLocationCell.swift +++ b/ios/MullvadVPN/SelectLocationCell.swift @@ -23,6 +23,7 @@ class SelectLocationCell: UITableViewCell { } return view }() + let tickImageView = UIImageView(image: UIImage(named: "IconTick")) let collapseButton = UIButton(type: .custom) @@ -118,9 +119,13 @@ class SelectLocationCell: UITableViewCell { collapseButton.accessibilityIdentifier = "CollapseButton" collapseButton.isAccessibilityElement = false collapseButton.tintColor = .white - collapseButton.addTarget(self, action: #selector(handleCollapseButton(_ :)), for: .touchUpInside) + collapseButton.addTarget( + self, + action: #selector(handleCollapseButton(_:)), + for: .touchUpInside + ) - [locationLabel, tickImageView, statusIndicator, collapseButton].forEach { (subview) in + [locationLabel, tickImageView, statusIndicator, collapseButton].forEach { subview in subview.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(subview) } @@ -132,7 +137,8 @@ class SelectLocationCell: UITableViewCell { setLayoutMargins() NSLayoutConstraint.activate([ - tickImageView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + tickImageView.leadingAnchor + .constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), tickImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), statusIndicator.widthAnchor.constraint(equalToConstant: kRelayIndicatorSize), @@ -140,16 +146,24 @@ class SelectLocationCell: UITableViewCell { statusIndicator.centerXAnchor.constraint(equalTo: tickImageView.centerXAnchor), statusIndicator.centerYAnchor.constraint(equalTo: tickImageView.centerYAnchor), - locationLabel.leadingAnchor.constraint(equalTo: statusIndicator.trailingAnchor, constant: 12), + locationLabel.leadingAnchor.constraint( + equalTo: statusIndicator.trailingAnchor, + constant: 12 + ), locationLabel.trailingAnchor.constraint(lessThanOrEqualTo: collapseButton.leadingAnchor) .withPriority(.defaultHigh), locationLabel.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), - locationLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), + locationLabel.bottomAnchor + .constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), - collapseButton.widthAnchor.constraint(equalToConstant: UIMetrics.contentLayoutMargins.left + UIMetrics.contentLayoutMargins.right + kCollapseButtonWidth), + collapseButton.widthAnchor + .constraint( + equalToConstant: UIMetrics.contentLayoutMargins.left + UIMetrics + .contentLayoutMargins.right + kCollapseButtonWidth + ), collapseButton.topAnchor.constraint(equalTo: contentView.topAnchor), collapseButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - collapseButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + collapseButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), ]) } @@ -239,7 +253,11 @@ class SelectLocationCell: UITableViewCell { ) accessibilityCustomActions = [ - UIAccessibilityCustomAction(name: actionName, target: self, selector: #selector(toggleCollapseAccessibilityAction)) + UIAccessibilityCustomAction( + name: actionName, + target: self, + selector: #selector(toggleCollapseAccessibilityAction) + ), ] } else { accessibilityCustomActions = nil diff --git a/ios/MullvadVPN/SelectLocationHeaderView.swift b/ios/MullvadVPN/SelectLocationHeaderView.swift index 89e2aa1077..9d39f73bad 100644 --- a/ios/MullvadVPN/SelectLocationHeaderView.swift +++ b/ios/MullvadVPN/SelectLocationHeaderView.swift @@ -9,7 +9,6 @@ import UIKit class SelectLocationHeaderView: UIView { - lazy var textContentLabel: UILabel = { let textLabel = UILabel() textLabel.translatesAutoresizingMaskIntoConstraints = false @@ -45,12 +44,11 @@ class SelectLocationHeaderView: UIView { textContentLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), textContentLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), textContentLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - textContentLabel.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + textContentLabel.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - } diff --git a/ios/MullvadVPN/SelectLocationNavigationController.swift b/ios/MullvadVPN/SelectLocationNavigationController.swift index 66a5c71fd1..b2e6605aec 100644 --- a/ios/MullvadVPN/SelectLocationNavigationController.swift +++ b/ios/MullvadVPN/SelectLocationNavigationController.swift @@ -10,7 +10,6 @@ import Foundation import UIKit class SelectLocationNavigationController: UINavigationController { - override var childForStatusBarStyle: UIViewController? { return topViewController } diff --git a/ios/MullvadVPN/SelectLocationViewController.swift b/ios/MullvadVPN/SelectLocationViewController.swift index cf27d8474e..68218c6d92 100644 --- a/ios/MullvadVPN/SelectLocationViewController.swift +++ b/ios/MullvadVPN/SelectLocationViewController.swift @@ -6,15 +6,17 @@ // Copyright © 2019 Mullvad VPN AB. All rights reserved. // -import UIKit import Logging +import UIKit protocol SelectLocationViewControllerDelegate: AnyObject { - func selectLocationViewController(_ controller: SelectLocationViewController, didSelectRelayLocation relayLocation: RelayLocation) + func selectLocationViewController( + _ controller: SelectLocationViewController, + didSelectRelayLocation relayLocation: RelayLocation + ) } class SelectLocationViewController: UIViewController, UITableViewDelegate { - static let cellReuseIdentifier = "Cell" private var tableView: UITableView? @@ -71,7 +73,10 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { tableView.estimatedRowHeight = 53 tableView.indicatorStyle = .white - tableView.register(SelectLocationCell.self, forCellReuseIdentifier: Self.cellReuseIdentifier) + tableView.register( + SelectLocationCell.self, + forCellReuseIdentifier: Self.cellReuseIdentifier + ) self.tableView = tableView @@ -84,10 +89,13 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { dataSource = LocationDataSource( tableView: tableView, - cellProvider: { [weak self] (tableView, indexPath, item) -> UITableViewCell? in + cellProvider: { [weak self] tableView, indexPath, item -> UITableViewCell? in guard let self = self else { return nil } - let cell = tableView.dequeueReusableCell(withIdentifier: Self.cellReuseIdentifier, for: indexPath) + let cell = tableView.dequeueReusableCell( + withIdentifier: Self.cellReuseIdentifier, + for: indexPath + ) as! SelectLocationCell cell.accessibilityIdentifier = item.location.stringRepresentation @@ -95,12 +103,13 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { cell.locationLabel.text = item.displayName cell.showsCollapseControl = item.isCollapsible cell.isExpanded = item.showsChildren - cell.didCollapseHandler = { [weak self] (cell) in + cell.didCollapseHandler = { [weak self] cell in self?.collapseCell(cell) } return cell - }) + } + ) tableView.delegate = self tableView.dataSource = dataSource @@ -108,23 +117,23 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { tableHeaderFooterViewTopConstraints = [ tableHeaderFooterView.topAnchor.constraint(equalTo: view.topAnchor), tableView.topAnchor.constraint(equalTo: tableHeaderFooterView.bottomAnchor), - tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ] tableHeaderFooterViewBottomConstraints = [ tableHeaderFooterView.bottomAnchor.constraint(equalTo: view.bottomAnchor), tableView.topAnchor.constraint(equalTo: view.topAnchor), - tableView.bottomAnchor.constraint(equalTo: tableHeaderFooterView.topAnchor) + tableView.bottomAnchor.constraint(equalTo: tableHeaderFooterView.topAnchor), ] NSLayoutConstraint.activate([ tableHeaderFooterView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableHeaderFooterView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor) + tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) setTableHeaderFooterConstraints() - if let setCachedRelaysOnViewDidLoad = self.setCachedRelaysOnViewDidLoad { + if let setCachedRelaysOnViewDidLoad = setCachedRelaysOnViewDidLoad { dataSource?.setRelays(setCachedRelaysOnViewDidLoad.relays) } @@ -143,10 +152,12 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { // Show header view at the bottom when controller is presented inline and show header view // at the top of the view when controller is presented modally. - showHeaderViewAtTheBottom = self.presentingViewController == nil + showHeaderViewAtTheBottom = presentingViewController == nil - if let indexPath = dataSource?.indexPathForSelectedRelay(), scrollToSelectedRelayOnViewWillAppear, !isViewAppeared { - self.tableView?.scrollToRow(at: indexPath, at: .middle, animated: false) + if let indexPath = dataSource?.indexPathForSelectedRelay(), + scrollToSelectedRelayOnViewWillAppear, !isViewAppeared + { + tableView?.scrollToRow(at: indexPath, at: .middle, animated: false) } } @@ -164,10 +175,13 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { isViewAppeared = false } - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + override func viewWillTransition( + to size: CGSize, + with coordinator: UIViewControllerTransitionCoordinator + ) { super.viewWillTransition(to: size, with: coordinator) - coordinator.animate { (context) in + coordinator.animate { context in if let indexPath = self.dataSource?.indexPathForSelectedRelay() { self.tableView?.scrollToRow(at: indexPath, at: .middle, animated: false) } @@ -190,8 +204,14 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { return dataSource?.item(for: indexPath)?.indentationLevel ?? 0 } - func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - if let item = dataSource?.item(for: indexPath), item.location == dataSource?.selectedRelayLocation { + func tableView( + _ tableView: UITableView, + willDisplay cell: UITableViewCell, + forRowAt indexPath: IndexPath + ) { + if let item = dataSource?.item(for: indexPath), + item.location == dataSource?.selectedRelayLocation + { cell.setSelected(true, animated: false) } } @@ -206,27 +226,31 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { scrollPosition: .none ) - self.delegate?.selectLocationViewController(self, didSelectRelayLocation: item.location) + delegate?.selectLocationViewController(self, didSelectRelayLocation: item.location) } // MARK: - Public func setCachedRelays(_ cachedRelays: RelayCache.CachedRelays) { guard isViewLoaded else { - self.setCachedRelaysOnViewDidLoad = cachedRelays + setCachedRelaysOnViewDidLoad = cachedRelays return } - self.dataSource?.setRelays(cachedRelays.relays) + dataSource?.setRelays(cachedRelays.relays) } - func setSelectedRelayLocation(_ relayLocation: RelayLocation?, animated: Bool, scrollPosition: UITableView.ScrollPosition) { + func setSelectedRelayLocation( + _ relayLocation: RelayLocation?, + animated: Bool, + scrollPosition: UITableView.ScrollPosition + ) { guard isViewLoaded else { - self.setRelayLocationOnViewDidLoad = relayLocation - self.setScrollPositionOnViewDidLoad = scrollPosition + setRelayLocationOnViewDidLoad = relayLocation + setScrollPositionOnViewDidLoad = scrollPosition return } - self.dataSource?.setSelectedRelayLocation( + dataSource?.setSelectedRelayLocation( relayLocation, showHiddenParents: true, animated: animated, @@ -238,7 +262,9 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { private func collapseCell(_ cell: SelectLocationCell) { guard let cellIndexPath = tableView?.indexPath(for: cell), - let dataSource = dataSource, let location = dataSource.relayLocation(for: cellIndexPath) else { + let dataSource = dataSource, + let location = dataSource.relayLocation(for: cellIndexPath) + else { return } @@ -250,8 +276,11 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { private func updateTableHeaderTopLayoutMargin() { // When contained within the navigation controller, we want the distance between the navigation title // and the table header label to be exactly 24pt. - if let navigationBar = navigationController?.navigationBar as? CustomNavigationBar, !showHeaderViewAtTheBottom { - tableHeaderFooterView.topLayoutMarginAdjustmentForNavigationBarTitle = navigationBar.titleLabelBottomInset + if let navigationBar = navigationController?.navigationBar as? CustomNavigationBar, + !showHeaderViewAtTheBottom + { + tableHeaderFooterView.topLayoutMarginAdjustmentForNavigationBarTitle = navigationBar + .titleLabelBottomInset } else { tableHeaderFooterView.topLayoutMarginAdjustmentForNavigationBarTitle = 0 } @@ -260,11 +289,13 @@ class SelectLocationViewController: UIViewController, UITableViewDelegate { private func setTableHeaderFooterConstraints() { if showHeaderViewAtTheBottom { NSLayoutConstraint.deactivate( - tableHeaderFooterViewTopConstraints) + tableHeaderFooterViewTopConstraints + ) NSLayoutConstraint.activate(tableHeaderFooterViewBottomConstraints) } else { NSLayoutConstraint.deactivate( - tableHeaderFooterViewBottomConstraints) + tableHeaderFooterViewBottomConstraints + ) NSLayoutConstraint.activate(tableHeaderFooterViewTopConstraints) } view.layoutIfNeeded() diff --git a/ios/MullvadVPN/SettingsAccountCell.swift b/ios/MullvadVPN/SettingsAccountCell.swift index 6021e4cca2..f0621f31dc 100644 --- a/ios/MullvadVPN/SettingsAccountCell.swift +++ b/ios/MullvadVPN/SettingsAccountCell.swift @@ -9,7 +9,6 @@ import UIKit class SettingsAccountCell: SettingsCell { - var accountExpiryDate: Date? { didSet { didUpdateAccountExpiry() diff --git a/ios/MullvadVPN/SettingsAddDNSEntryCell.swift b/ios/MullvadVPN/SettingsAddDNSEntryCell.swift index a2db42e6ba..3aaadd90f7 100644 --- a/ios/MullvadVPN/SettingsAddDNSEntryCell.swift +++ b/ios/MullvadVPN/SettingsAddDNSEntryCell.swift @@ -16,7 +16,10 @@ class SettingsAddDNSEntryCell: SettingsCell { backgroundView?.backgroundColor = UIColor.SubSubCell.backgroundColor - let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) + let gestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(handleTap(_:)) + ) contentView.addGestureRecognizer(gestureRecognizer) } diff --git a/ios/MullvadVPN/SettingsCell.swift b/ios/MullvadVPN/SettingsCell.swift index 2bb7a8a43f..3aaf1ee67b 100644 --- a/ios/MullvadVPN/SettingsCell.swift +++ b/ios/MullvadVPN/SettingsCell.swift @@ -26,7 +26,6 @@ enum SettingsDisclosureType { } class SettingsCell: UITableViewCell { - let titleLabel = UILabel() let detailTitleLabel = UILabel() let disclosureImageView = UIImageView(image: nil) @@ -83,15 +82,23 @@ class SettingsCell: UITableViewCell { setLayoutMargins() NSLayoutConstraint.activate([ - titleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + titleLabel.leadingAnchor + .constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), titleLabel.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), - titleLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), + titleLabel.bottomAnchor + .constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), - detailTitleLabel.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: titleLabel.trailingAnchor, multiplier: 1), + detailTitleLabel.leadingAnchor.constraint( + greaterThanOrEqualToSystemSpacingAfter: titleLabel.trailingAnchor, + multiplier: 1 + ), - detailTitleLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), - detailTitleLabel.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), - detailTitleLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), + detailTitleLabel.trailingAnchor + .constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), + detailTitleLabel.topAnchor + .constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), + detailTitleLabel.bottomAnchor + .constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), ]) } @@ -134,7 +141,9 @@ class SettingsCell: UITableViewCell { for subview in subviews { // Detect the edit control and move it, so that the nested image view is aligned along the left edge of the // layout margins. - if subview.description.starts(with: "<UITableViewCellEditControl"), let imageView = subview.subviews.first { + if subview.description.starts(with: "<UITableViewCellEditControl"), + let imageView = subview.subviews.first + { let imageOffset = imageView.frame.minX var pos = subview.frame.origin pos.x = layoutMargins.left - imageOffset diff --git a/ios/MullvadVPN/SettingsDNSTextCell.swift b/ios/MullvadVPN/SettingsDNSTextCell.swift index 1d459c9229..dd1a97ccd3 100644 --- a/ios/MullvadVPN/SettingsDNSTextCell.swift +++ b/ios/MullvadVPN/SettingsDNSTextCell.swift @@ -10,8 +10,7 @@ import Foundation import UIKit class SettingsDNSTextCell: SettingsCell, UITextFieldDelegate { - - var isValidInput: Bool = true { + var isValidInput = true { didSet { updateCellAppearance(animated: false) } @@ -65,7 +64,7 @@ class SettingsDNSTextCell: SettingsCell, UITextFieldDelegate { textField.topAnchor.constraint(equalTo: contentView.topAnchor), textField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - textField.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + textField.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), ]) updateCellAppearance(animated: false) @@ -130,7 +129,11 @@ class SettingsDNSTextCell: SettingsCell, UITextFieldDelegate { return true } - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { let ipv4AddressCharset = CharacterSet.ipv4AddressCharset let ipv6AddressCharset = CharacterSet.ipv6AddressCharset @@ -140,5 +143,4 @@ class SettingsDNSTextCell: SettingsCell, UITextFieldDelegate { } } } - } diff --git a/ios/MullvadVPN/SettingsDataSource.swift b/ios/MullvadVPN/SettingsDataSource.swift index 203d446b5f..1a9caf3389 100644 --- a/ios/MullvadVPN/SettingsDataSource.swift +++ b/ios/MullvadVPN/SettingsDataSource.swift @@ -74,11 +74,17 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab private func registerClasses() { CellReuseIdentifiers.allCases.forEach { cellIdentifier in - tableView?.register(cellIdentifier.reusableViewClass, forCellReuseIdentifier: cellIdentifier.rawValue) + tableView?.register( + cellIdentifier.reusableViewClass, + forCellReuseIdentifier: cellIdentifier.rawValue + ) } HeaderFooterReuseIdentifier.allCases.forEach { reuseIdentifier in - tableView?.register(reuseIdentifier.reusableViewClass, forHeaderFooterViewReuseIdentifier: reuseIdentifier.rawValue) + tableView?.register( + reuseIdentifier.reusableViewClass, + forHeaderFooterViewReuseIdentifier: reuseIdentifier.rawValue + ) } } @@ -114,7 +120,10 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab switch item { case .account: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.accountCell.rawValue, for: indexPath) as! SettingsAccountCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.accountCell.rawValue, + for: indexPath + ) as! SettingsAccountCell cell.titleLabel.text = NSLocalizedString( "ACCOUNT_CELL_LABEL", tableName: "Settings", @@ -128,7 +137,10 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab return cell case .preferences: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.basicCell.rawValue, for: indexPath) as! SettingsCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.basicCell.rawValue, + for: indexPath + ) as! SettingsCell cell.titleLabel.text = NSLocalizedString( "PREFERENCES_CELL_LABEL", tableName: "Settings", @@ -142,7 +154,10 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab return cell case .wireguardKey: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.basicCell.rawValue, for: indexPath) as! SettingsCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.basicCell.rawValue, + for: indexPath + ) as! SettingsCell cell.titleLabel.text = NSLocalizedString( "WIREGUARD_KEY_CELL_LABEL", tableName: "Settings", @@ -156,7 +171,10 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab return cell case .version: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.basicCell.rawValue, for: indexPath) as! SettingsCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.basicCell.rawValue, + for: indexPath + ) as! SettingsCell cell.titleLabel.text = NSLocalizedString( "APP_VERSION_CELL_LABEL", tableName: "Settings", @@ -170,7 +188,10 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab return cell case .problemReport: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.basicCell.rawValue, for: indexPath) as! SettingsCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.basicCell.rawValue, + for: indexPath + ) as! SettingsCell cell.titleLabel.text = NSLocalizedString( "REPORT_PROBLEM_CELL_LABEL", tableName: "Settings", @@ -184,7 +205,10 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab return cell case .faq: - let cell = tableView.dequeueReusableCell(withIdentifier: CellReuseIdentifiers.basicCell.rawValue, for: indexPath) as! SettingsCell + let cell = tableView.dequeueReusableCell( + withIdentifier: CellReuseIdentifiers.basicCell.rawValue, + for: indexPath + ) as! SettingsCell cell.titleLabel.text = NSLocalizedString( "FAQ_AND_GUIDES_CELL_LABEL", tableName: "Settings", @@ -196,7 +220,6 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab cell.disclosureType = .externalLink return cell - } } @@ -217,7 +240,11 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - return tableView.dequeueReusableHeaderFooterView(withIdentifier: HeaderFooterReuseIdentifier.spacer.rawValue) + return tableView + .dequeueReusableHeaderFooterView( + withIdentifier: HeaderFooterReuseIdentifier.spacer + .rawValue + ) } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { @@ -255,7 +282,8 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab // Refresh individual row if expiry changed. if let newAccountData = newAccountData, let oldAccountData = oldAccountData, oldAccountData.number == newAccountData.number, - oldAccountData.expiry != newAccountData.expiry { + oldAccountData.expiry != newAccountData.expiry + { tableView?.performBatchUpdates { if let indexPath = snapshot.indexPathForItem(.account) { tableView?.reloadRows(at: [indexPath], with: .none) @@ -268,7 +296,10 @@ class SettingsDataSource: NSObject, TunnelObserver, UITableViewDataSource, UITab tableView?.reloadData() } - func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2) { + func tunnelManager( + _ manager: TunnelManager, + didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2 + ) { // no-op } } diff --git a/ios/MullvadVPN/SettingsDataSourceDelegate.swift b/ios/MullvadVPN/SettingsDataSourceDelegate.swift index 739c6d4552..30d7e1629a 100644 --- a/ios/MullvadVPN/SettingsDataSourceDelegate.swift +++ b/ios/MullvadVPN/SettingsDataSourceDelegate.swift @@ -9,5 +9,8 @@ import UIKit protocol SettingsDataSourceDelegate: AnyObject { - func settingsDataSource(_ dataSource: SettingsDataSource, didSelectItem item: SettingsDataSource.Item) + func settingsDataSource( + _ dataSource: SettingsDataSource, + didSelectItem item: SettingsDataSource.Item + ) } diff --git a/ios/MullvadVPN/SettingsManager/SettingsManager.swift b/ios/MullvadVPN/SettingsManager/SettingsManager.swift index 54aa293615..95af7b2a99 100644 --- a/ios/MullvadVPN/SettingsManager/SettingsManager.swift +++ b/ios/MullvadVPN/SettingsManager/SettingsManager.swift @@ -41,7 +41,6 @@ struct StringEncodingError: LocalizedError { } extension SettingsManager { - // MARK: - Lsat used account static func getLastUsedAccount() throws -> String { @@ -104,7 +103,6 @@ extension SettingsManager { try addOrUpdateItem(.deviceState, data: data) } - static func deleteDeviceState() throws { try deleteItem(.deviceState) } @@ -135,6 +133,7 @@ extension SettingsManager { throw KeychainError(code: status) } } + private static func addOrUpdateItem(_ item: Item, data: Data) throws { do { try updateItem(item, data: data) @@ -167,18 +166,18 @@ extension SettingsManager { } } - private static func createDefaultAttributes(item: Item) -> [CFString: Any] { + private static func createDefaultAttributes(item: Item) -> [CFString: Any] { return [ kSecClass: kSecClassGenericPassword, kSecAttrService: keychainServiceName, - kSecAttrAccount: item.rawValue + kSecAttrAccount: item.rawValue, ] } private static func createAccessAttributes() -> [CFString: Any] { return [ kSecAttrAccessGroup: ApplicationConfiguration.securityGroupIdentifier, - kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock + kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock, ] } @@ -192,7 +191,7 @@ extension SettingsManager { kSecAttrService: keychainServiceName, kSecReturnAttributes: true, kSecReturnData: true, - kSecMatchLimit: kSecMatchLimitAll + kSecMatchLimit: kSecMatchLimitAll, ] var result: CFTypeRef? @@ -209,9 +208,10 @@ extension SettingsManager { return items.filter(Self.filterLegacySettings) .compactMap { item -> LegacyTunnelSettings? in guard let accountNumber = item[kSecAttrAccount] as? String, - let data = item[kSecValueData] as? Data else { - return nil - } + let data = item[kSecValueData] as? Data + else { + return nil + } do { let tunnelSettings = try JSONDecoder().decode( TunnelSettingsV1.self, @@ -237,7 +237,7 @@ extension SettingsManager { kSecClass: kSecClassGenericPassword, kSecAttrService: keychainServiceName, kSecReturnAttributes: true, - kSecMatchLimit: kSecMatchLimitAll + kSecMatchLimit: kSecMatchLimitAll, ] var result: CFTypeRef? @@ -262,7 +262,7 @@ extension SettingsManager { items.filter(Self.filterLegacySettings) .enumerated() - .forEach { (index, item) in + .forEach { index, item in guard let account = item[kSecAttrAccount] else { return } @@ -270,7 +270,7 @@ extension SettingsManager { let deleteQuery: [CFString: Any] = [ kSecClass: kSecClassGenericPassword, kSecAttrService: keychainServiceName, - kSecAttrAccount: account + kSecAttrAccount: account, ] let status = SecItemDelete(deleteQuery as CFDictionary) diff --git a/ios/MullvadVPN/SettingsManager/TunnelSettingsV1.swift b/ios/MullvadVPN/SettingsManager/TunnelSettingsV1.swift index 0d7facb554..b5a8ed207f 100644 --- a/ios/MullvadVPN/SettingsManager/TunnelSettingsV1.swift +++ b/ios/MullvadVPN/SettingsManager/TunnelSettingsV1.swift @@ -8,9 +8,9 @@ import Foundation import struct Network.IPv4Address -import class WireGuardKitTypes.PublicKey -import class WireGuardKitTypes.PrivateKey import struct WireGuardKitTypes.IPAddressRange +import class WireGuardKitTypes.PrivateKey +import class WireGuardKitTypes.PublicKey /// A struct that holds the configuration passed via `NETunnelProviderProtocol`. struct TunnelSettingsV1: Codable, Equatable { @@ -35,8 +35,7 @@ struct InterfaceSettings: Codable, Equatable { nextPrivateKey: PrivateKeyWithMetadata? = nil, addresses: [IPAddressRange] = [], dnsSettings: DNSSettings = DNSSettings() - ) - { + ) { self.privateKey = privateKey self.nextPrivateKey = nextPrivateKey self.addresses = addresses @@ -50,7 +49,10 @@ struct InterfaceSettings: Codable, Equatable { addresses = try container.decode([IPAddressRange].self, forKey: .addresses) // Added in 2022.1 - nextPrivateKey = try container.decodeIfPresent(PrivateKeyWithMetadata.self, forKey: .nextPrivateKey) + nextPrivateKey = try container.decodeIfPresent( + PrivateKeyWithMetadata.self, + forKey: .nextPrivateKey + ) // Provide default value, since `dnsSettings` key does not exist in <= 2021.2 dnsSettings = try container.decodeIfPresent(DNSSettings.self, forKey: .dnsSettings) diff --git a/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift index d21194c92f..c38dc00ddb 100644 --- a/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift +++ b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift @@ -8,16 +8,16 @@ import Foundation import struct Network.IPv4Address -import class WireGuardKitTypes.PublicKey -import class WireGuardKitTypes.PrivateKey import struct WireGuardKitTypes.IPAddressRange +import class WireGuardKitTypes.PrivateKey +import class WireGuardKitTypes.PublicKey struct TunnelSettingsV2: Codable, Equatable { /// Relay constraints. - var relayConstraints: RelayConstraints = RelayConstraints() + var relayConstraints = RelayConstraints() /// DNS settings. - var dnsSettings: DNSSettings = DNSSettings() + var dnsSettings = DNSSettings() } struct StoredAccountData: Codable, Equatable { @@ -52,7 +52,7 @@ enum DeviceState: Codable, Equatable { var accountData: StoredAccountData? { switch self { - case .loggedIn(let accountData, _): + case let .loggedIn(accountData, _): return accountData case .loggedOut, .revoked: return nil @@ -61,7 +61,7 @@ enum DeviceState: Codable, Equatable { var deviceData: StoredDeviceData? { switch self { - case .loggedIn(_, let deviceData): + case let .loggedIn(_, deviceData): return deviceData case .loggedOut, .revoked: return nil diff --git a/ios/MullvadVPN/SettingsNavigationController.swift b/ios/MullvadVPN/SettingsNavigationController.swift index b916561373..6611759c48 100644 --- a/ios/MullvadVPN/SettingsNavigationController.swift +++ b/ios/MullvadVPN/SettingsNavigationController.swift @@ -22,11 +22,15 @@ enum SettingsDismissReason { } protocol SettingsNavigationControllerDelegate: AnyObject { - func settingsNavigationController(_ controller: SettingsNavigationController, didFinishWithReason reason: SettingsDismissReason) + func settingsNavigationController( + _ controller: SettingsNavigationController, + didFinishWithReason reason: SettingsDismissReason + ) } -class SettingsNavigationController: CustomNavigationController, SettingsViewControllerDelegate, AccountViewControllerDelegate, UIAdaptivePresentationControllerDelegate { - +class SettingsNavigationController: CustomNavigationController, SettingsViewControllerDelegate, + AccountViewControllerDelegate, UIAdaptivePresentationControllerDelegate +{ weak var settingsDelegate: SettingsNavigationControllerDelegate? override var childForStatusBarStyle: UIViewController? { @@ -68,20 +72,20 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont // MARK: - SettingsViewControllerDelegate func settingsViewControllerDidFinish(_ controller: SettingsViewController) { - self.settingsDelegate?.settingsNavigationController(self, didFinishWithReason: .none) + settingsDelegate?.settingsNavigationController(self, didFinishWithReason: .none) } // MARK: - AccountViewControllerDelegate func accountViewControllerDidLogout(_ controller: AccountViewController) { - self.settingsDelegate?.settingsNavigationController(self, didFinishWithReason: .userLoggedOut) + settingsDelegate?.settingsNavigationController(self, didFinishWithReason: .userLoggedOut) } // MARK: - Navigation func navigate(to route: SettingsNavigationRoute, animated: Bool) { let nextViewController = makeViewController(for: route) - if let rootController = self.viewControllers.first, viewControllers.count > 1 { + if let rootController = viewControllers.first, viewControllers.count > 1 { setViewControllers([rootController, nextViewController], animated: animated) } else { pushViewController(nextViewController, animated: animated) diff --git a/ios/MullvadVPN/SettingsStaticTextFooterView.swift b/ios/MullvadVPN/SettingsStaticTextFooterView.swift index 7e5619fb4d..700d31dc78 100644 --- a/ios/MullvadVPN/SettingsStaticTextFooterView.swift +++ b/ios/MullvadVPN/SettingsStaticTextFooterView.swift @@ -26,10 +26,12 @@ class SettingsStaticTextFooterView: UITableViewHeaderFooterView { contentView.addConstraints([ titleLabel.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), - titleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), - titleLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), + titleLabel.leadingAnchor + .constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + titleLabel.trailingAnchor + .constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), titleLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor) - .withPriority(.defaultLow) + .withPriority(.defaultLow), ]) } diff --git a/ios/MullvadVPN/SettingsSwitchCell.swift b/ios/MullvadVPN/SettingsSwitchCell.swift index a504726dff..5b81c186b4 100644 --- a/ios/MullvadVPN/SettingsSwitchCell.swift +++ b/ios/MullvadVPN/SettingsSwitchCell.swift @@ -9,7 +9,6 @@ import UIKit class SettingsSwitchCell: SettingsCell { - private let switchContainer = CustomSwitchContainer() var action: ((Bool) -> Void)? @@ -19,7 +18,11 @@ class SettingsSwitchCell: SettingsCell { accessoryView = switchContainer - switchContainer.control.addTarget(self, action: #selector(switchValueDidChange), for: .valueChanged) + switchContainer.control.addTarget( + self, + action: #selector(switchValueDidChange), + for: .valueChanged + ) isAccessibilityElement = true } @@ -45,7 +48,7 @@ class SettingsSwitchCell: SettingsCell { // MARK: - Actions @objc private func switchValueDidChange() { - action?(self.switchContainer.control.isOn) + action?(switchContainer.control.isOn) } // MARK: - Accessibility @@ -99,7 +102,7 @@ class SettingsSwitchCell: SettingsCell { override func accessibilityActivate() -> Bool { guard switchContainer.isEnabled else { return false } - let newValue = !self.switchContainer.control.isOn + let newValue = !switchContainer.control.isOn setOn(newValue, animated: true) action?(newValue) diff --git a/ios/MullvadVPN/SettingsViewController.swift b/ios/MullvadVPN/SettingsViewController.swift index e6dd7490a7..6fa1bbf045 100644 --- a/ios/MullvadVPN/SettingsViewController.swift +++ b/ios/MullvadVPN/SettingsViewController.swift @@ -14,8 +14,9 @@ protocol SettingsViewControllerDelegate: AnyObject { func settingsViewControllerDidFinish(_ controller: SettingsViewController) } -class SettingsViewController: UITableViewController, SettingsDataSourceDelegate, SFSafariViewControllerDelegate { - +class SettingsViewController: UITableViewController, SettingsDataSourceDelegate, + SFSafariViewControllerDelegate +{ weak var delegate: SettingsViewControllerDelegate? override var preferredStatusBarStyle: UIStatusBarStyle { @@ -42,7 +43,11 @@ class SettingsViewController: UITableViewController, SettingsDataSourceDelegate, comment: "" ) navigationItem.largeTitleDisplayMode = .always - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDismiss)) + navigationItem.rightBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .done, + target: self, + action: #selector(handleDismiss) + ) tableView.backgroundColor = .secondaryColor tableView.separatorColor = .secondaryColor @@ -61,13 +66,19 @@ class SettingsViewController: UITableViewController, SettingsDataSourceDelegate, // MARK: - SettingsDataSourceDelegate - func settingsDataSource(_ dataSource: SettingsDataSource, didSelectItem item: SettingsDataSource.Item) { + func settingsDataSource( + _ dataSource: SettingsDataSource, + didSelectItem item: SettingsDataSource.Item + ) { if let route = item.navigationRoute { let settingsNavigationController = navigationController as? SettingsNavigationController settingsNavigationController?.navigate(to: route, animated: true) } else if case .faq = item { - let safariViewController = SFSafariViewController(url: ApplicationConfiguration.faqAndGuidesURL) + let safariViewController = SFSafariViewController( + url: ApplicationConfiguration + .faqAndGuidesURL + ) safariViewController.delegate = self present(safariViewController, animated: true) diff --git a/ios/MullvadVPN/SimulatorTunnelProvider.swift b/ios/MullvadVPN/SimulatorTunnelProvider.swift index 657337f82c..162a95590f 100644 --- a/ios/MullvadVPN/SimulatorTunnelProvider.swift +++ b/ios/MullvadVPN/SimulatorTunnelProvider.swift @@ -48,374 +48,388 @@ extension NETunnelProviderManager: VPNTunnelProviderManagerProtocol {} #if targetEnvironment(simulator) -// MARK: - NEPacketTunnelProvider stubs + // MARK: - NEPacketTunnelProvider stubs -class SimulatorTunnelProviderDelegate { - fileprivate(set) var connection: SimulatorVPNConnection? + class SimulatorTunnelProviderDelegate { + fileprivate(set) var connection: SimulatorVPNConnection? - var protocolConfiguration: NEVPNProtocol { - return connection?.protocolConfiguration ?? NEVPNProtocol() - } - - var reasserting: Bool { - get { - return connection?.reasserting ?? false + var protocolConfiguration: NEVPNProtocol { + return connection?.protocolConfiguration ?? NEVPNProtocol() } - set { - connection?.reasserting = newValue + + var reasserting: Bool { + get { + return connection?.reasserting ?? false + } + set { + connection?.reasserting = newValue + } } - } - func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { - completionHandler(nil) - } + func startTunnel( + options: [String: NSObject]?, + completionHandler: @escaping (Error?) -> Void + ) { + completionHandler(nil) + } - func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - completionHandler() - } + func stopTunnel( + with reason: NEProviderStopReason, + completionHandler: @escaping () -> Void + ) { + completionHandler() + } - func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { - completionHandler?(nil) + func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { + completionHandler?(nil) + } } -} -class SimulatorTunnelProvider { - static let shared = SimulatorTunnelProvider() + class SimulatorTunnelProvider { + static let shared = SimulatorTunnelProvider() - private let lock = NSLock() - private var _delegate: SimulatorTunnelProviderDelegate? + private let lock = NSLock() + private var _delegate: SimulatorTunnelProviderDelegate? - var delegate: SimulatorTunnelProviderDelegate! { - get { - lock.lock() - defer { lock.unlock() } + var delegate: SimulatorTunnelProviderDelegate! { + get { + lock.lock() + defer { lock.unlock() } - return _delegate - } - set { - lock.lock() - _delegate = newValue - lock.unlock() + return _delegate + } + set { + lock.lock() + _delegate = newValue + lock.unlock() + } } - } - private init() {} + private init() {} - fileprivate func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { - self.delegate.handleAppMessage(messageData, completionHandler: completionHandler) + fileprivate func handleAppMessage( + _ messageData: Data, + completionHandler: ((Data?) -> Void)? = nil + ) { + delegate.handleAppMessage(messageData, completionHandler: completionHandler) + } } -} -// MARK: - NEVPNConnection stubs + // MARK: - NEVPNConnection stubs -class SimulatorVPNConnection: NSObject, VPNConnectionProtocol { - // Protocol configuration is automatically synced by `SimulatorTunnelInfo` - fileprivate var protocolConfiguration = NEVPNProtocol() + class SimulatorVPNConnection: NSObject, VPNConnectionProtocol { + // Protocol configuration is automatically synced by `SimulatorTunnelInfo` + fileprivate var protocolConfiguration = NEVPNProtocol() - private let lock = NSRecursiveLock() - private var _status: NEVPNStatus = .disconnected - private var _reasserting = false - private var _connectedDate: Date? + private let lock = NSRecursiveLock() + private var _status: NEVPNStatus = .disconnected + private var _reasserting = false + private var _connectedDate: Date? - private(set) var status: NEVPNStatus { - get { - lock.lock() - defer { lock.unlock() } + private(set) var status: NEVPNStatus { + get { + lock.lock() + defer { lock.unlock() } - return _status - } - set { - lock.lock() + return _status + } + set { + lock.lock() - if _status != newValue { - _status = newValue + if _status != newValue { + _status = newValue - // Send notification while holding the lock. This should enable the receiver - // to fetch the `SimulatorVPNConnection.status` before the concurrent code gets - // opportunity to change it again. - postStatusDidChangeNotification() - } + // Send notification while holding the lock. This should enable the receiver + // to fetch the `SimulatorVPNConnection.status` before the concurrent code gets + // opportunity to change it again. + postStatusDidChangeNotification() + } - lock.unlock() + lock.unlock() + } } - } - var reasserting: Bool { - get { - lock.lock() - defer { lock.unlock() } + var reasserting: Bool { + get { + lock.lock() + defer { lock.unlock() } - return _reasserting - } - set { - lock.lock() + return _reasserting + } + set { + lock.lock() - if _reasserting != newValue { - _reasserting = newValue + if _reasserting != newValue { + _reasserting = newValue - if newValue { - status = .reasserting - } else { - status = .connected + if newValue { + status = .reasserting + } else { + status = .connected + } } - } - lock.unlock() + lock.unlock() + } } - } - private(set) var connectedDate: Date? { - get { - lock.lock() - defer { lock.unlock() } + private(set) var connectedDate: Date? { + get { + lock.lock() + defer { lock.unlock() } - return _connectedDate + return _connectedDate + } + set { + lock.lock() + _connectedDate = newValue + lock.unlock() + } } - set { - lock.lock() - _connectedDate = newValue - lock.unlock() + + func startVPNTunnel() throws { + try startVPNTunnel(options: nil) } - } - func startVPNTunnel() throws { - try startVPNTunnel(options: nil) - } + func startVPNTunnel(options: [String: NSObject]?) throws { + SimulatorTunnelProvider.shared.delegate.connection = self - func startVPNTunnel(options: [String: NSObject]?) throws { - SimulatorTunnelProvider.shared.delegate.connection = self + status = .connecting - status = .connecting + SimulatorTunnelProvider.shared.delegate.startTunnel(options: options) { error in + if error == nil { + self.status = .connected + self.connectedDate = Date() + } else { + self.status = .disconnected + self.connectedDate = nil + } + } + } - SimulatorTunnelProvider.shared.delegate.startTunnel(options: options) { error in - if error == nil { - self.status = .connected - self.connectedDate = Date() - } else { + func stopVPNTunnel() { + status = .disconnecting + + SimulatorTunnelProvider.shared.delegate.stopTunnel(with: .userInitiated) { self.status = .disconnected self.connectedDate = nil } } - } - - func stopVPNTunnel() { - status = .disconnecting - SimulatorTunnelProvider.shared.delegate.stopTunnel(with: .userInitiated) { - self.status = .disconnected - self.connectedDate = nil + private func postStatusDidChangeNotification() { + NotificationCenter.default.post(name: .NEVPNStatusDidChange, object: self) } } - private func postStatusDidChangeNotification() { - NotificationCenter.default.post(name: .NEVPNStatusDidChange, object: self) - } -} - -// MARK: - NETunnelProviderSession stubs - -class SimulatorTunnelProviderSession: SimulatorVPNConnection, VPNTunnelProviderSessionProtocol { + // MARK: - NETunnelProviderSession stubs - func sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)?) throws { - SimulatorTunnelProvider.shared.handleAppMessage(messageData, completionHandler: responseHandler) + class SimulatorTunnelProviderSession: SimulatorVPNConnection, VPNTunnelProviderSessionProtocol { + func sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)?) throws { + SimulatorTunnelProvider.shared.handleAppMessage( + messageData, + completionHandler: responseHandler + ) + } } -} - -// MARK: - NETunnelProviderManager stubs + // MARK: - NETunnelProviderManager stubs -/// A mock struct for tunnel configuration and connection -private struct SimulatorTunnelInfo { - /// A unique identifier for the configuration - var identifier = UUID().uuidString + /// A mock struct for tunnel configuration and connection + private struct SimulatorTunnelInfo { + /// A unique identifier for the configuration + var identifier = UUID().uuidString - /// An associated VPN connection. - /// Intentionally initialized with a `SimulatorTunnelProviderSession` subclass which - /// implements the necessary protocol - var connection: SimulatorVPNConnection = SimulatorTunnelProviderSession() + /// An associated VPN connection. + /// Intentionally initialized with a `SimulatorTunnelProviderSession` subclass which + /// implements the necessary protocol + var connection: SimulatorVPNConnection = SimulatorTunnelProviderSession() - /// Whether configuration is enabled - var isEnabled = false + /// Whether configuration is enabled + var isEnabled = false - /// Whether on-demand VPN is enabled - var isOnDemandEnabled = false + /// Whether on-demand VPN is enabled + var isOnDemandEnabled = false - /// On-demand VPN rules - var onDemandRules = [NEOnDemandRule]() + /// On-demand VPN rules + var onDemandRules = [NEOnDemandRule]() - /// Protocol configuration - var protocolConfiguration: NEVPNProtocol? { - didSet { - self.connection.protocolConfiguration = protocolConfiguration ?? NEVPNProtocol() + /// Protocol configuration + var protocolConfiguration: NEVPNProtocol? { + didSet { + connection.protocolConfiguration = protocolConfiguration ?? NEVPNProtocol() + } } - } - - /// Tunnel description - var localizedDescription: String? - - /// Designated initializer - init() {} -} - -class SimulatorTunnelProviderManager: VPNTunnelProviderManagerProtocol, Equatable { - static let tunnelsLock = NSRecursiveLock() - fileprivate static var tunnels = [SimulatorTunnelInfo]() + /// Tunnel description + var localizedDescription: String? - private let lock = NSLock() - private var tunnelInfo: SimulatorTunnelInfo - private var identifier: String { - lock.lock() - defer { lock.unlock() } - - return tunnelInfo.identifier + /// Designated initializer + init() {} } - var isOnDemandEnabled: Bool { - get { + class SimulatorTunnelProviderManager: VPNTunnelProviderManagerProtocol, Equatable { + static let tunnelsLock = NSRecursiveLock() + fileprivate static var tunnels = [SimulatorTunnelInfo]() + + private let lock = NSLock() + private var tunnelInfo: SimulatorTunnelInfo + private var identifier: String { lock.lock() defer { lock.unlock() } - return tunnelInfo.isOnDemandEnabled + return tunnelInfo.identifier } - set { - lock.lock() - tunnelInfo.isOnDemandEnabled = newValue - lock.unlock() - } - } - var onDemandRules: [NEOnDemandRule] { - get { - lock.lock() - defer { lock.unlock() } + var isOnDemandEnabled: Bool { + get { + lock.lock() + defer { lock.unlock() } - return tunnelInfo.onDemandRules - } - set { - lock.lock() - tunnelInfo.onDemandRules = newValue - lock.unlock() + return tunnelInfo.isOnDemandEnabled + } + set { + lock.lock() + tunnelInfo.isOnDemandEnabled = newValue + lock.unlock() + } } - } - var isEnabled: Bool { - get { - lock.lock() - defer { lock.unlock() } + var onDemandRules: [NEOnDemandRule] { + get { + lock.lock() + defer { lock.unlock() } - return tunnelInfo.isEnabled + return tunnelInfo.onDemandRules + } + set { + lock.lock() + tunnelInfo.onDemandRules = newValue + lock.unlock() + } } - set { - lock.lock() - tunnelInfo.isEnabled = newValue - lock.unlock() + + var isEnabled: Bool { + get { + lock.lock() + defer { lock.unlock() } + + return tunnelInfo.isEnabled + } + set { + lock.lock() + tunnelInfo.isEnabled = newValue + lock.unlock() + } } - } - var protocolConfiguration: NEVPNProtocol? { - get { - lock.lock() - defer { lock.unlock() } + var protocolConfiguration: NEVPNProtocol? { + get { + lock.lock() + defer { lock.unlock() } - return tunnelInfo.protocolConfiguration + return tunnelInfo.protocolConfiguration + } + set { + lock.lock() + tunnelInfo.protocolConfiguration = newValue + lock.unlock() + } } - set { - lock.lock() - tunnelInfo.protocolConfiguration = newValue - lock.unlock() + + var localizedDescription: String? { + get { + lock.lock() + defer { lock.unlock() } + + return tunnelInfo.localizedDescription + } + set { + lock.lock() + tunnelInfo.localizedDescription = newValue + lock.unlock() + } } - } - var localizedDescription: String? { - get { + var connection: SimulatorVPNConnection { lock.lock() defer { lock.unlock() } - return tunnelInfo.localizedDescription - } - set { - lock.lock() - tunnelInfo.localizedDescription = newValue - lock.unlock() + return tunnelInfo.connection } - } - var connection: SimulatorVPNConnection { - lock.lock() - defer { lock.unlock() } + static func loadAllFromPreferences(completionHandler: ( + [SimulatorTunnelProviderManager]?, + Error? + ) -> Void) { + Self.tunnelsLock.lock() + let tunnelProviders = tunnels.map { tunnelInfo in + return SimulatorTunnelProviderManager(tunnelInfo: tunnelInfo) + } + Self.tunnelsLock.unlock() - return tunnelInfo.connection - } + completionHandler(tunnelProviders, nil) + } - static func loadAllFromPreferences(completionHandler: ([SimulatorTunnelProviderManager]?, Error?) -> Void) { - Self.tunnelsLock.lock() - let tunnelProviders = tunnels.map { tunnelInfo in - return SimulatorTunnelProviderManager(tunnelInfo: tunnelInfo) + required convenience init() { + self.init(tunnelInfo: SimulatorTunnelInfo()) } - Self.tunnelsLock.unlock() - completionHandler(tunnelProviders, nil) - } + private init(tunnelInfo: SimulatorTunnelInfo) { + self.tunnelInfo = tunnelInfo + } - required convenience init() { - self.init(tunnelInfo: SimulatorTunnelInfo()) - } + func loadFromPreferences(completionHandler: (Error?) -> Void) { + var error: NEVPNError? - private init(tunnelInfo: SimulatorTunnelInfo) { - self.tunnelInfo = tunnelInfo - } + Self.tunnelsLock.lock() - func loadFromPreferences(completionHandler: (Error?) -> Void) { - var error: NEVPNError? + if let savedTunnel = Self.tunnels.first(where: { $0.identifier == self.identifier }) { + tunnelInfo = savedTunnel + } else { + error = NEVPNError(.configurationInvalid) + } - Self.tunnelsLock.lock() + Self.tunnelsLock.unlock() - if let savedTunnel = Self.tunnels.first(where: { $0.identifier == self.identifier }) { - self.tunnelInfo = savedTunnel - } else { - error = NEVPNError(.configurationInvalid) + completionHandler(error) } - Self.tunnelsLock.unlock() + func saveToPreferences(completionHandler: ((Error?) -> Void)?) { + Self.tunnelsLock.lock() - completionHandler(error) - } + if let index = Self.tunnels.firstIndex(where: { $0.identifier == self.identifier }) { + Self.tunnels[index] = tunnelInfo + } else { + Self.tunnels.append(tunnelInfo) + } - func saveToPreferences(completionHandler: ((Error?) -> Void)?) { - Self.tunnelsLock.lock() + Self.tunnelsLock.unlock() - if let index = Self.tunnels.firstIndex(where: { $0.identifier == self.identifier }) { - Self.tunnels[index] = self.tunnelInfo - } else { - Self.tunnels.append(self.tunnelInfo) + completionHandler?(nil) } - Self.tunnelsLock.unlock() + func removeFromPreferences(completionHandler: ((Error?) -> Void)?) { + var error: NEVPNError? - completionHandler?(nil) - } + Self.tunnelsLock.lock() - func removeFromPreferences(completionHandler: ((Error?) -> Void)?) { - var error: NEVPNError? + if let index = Self.tunnels.firstIndex(where: { $0.identifier == self.identifier }) { + Self.tunnels.remove(at: index) + } else { + error = NEVPNError(.configurationReadWriteFailed) + } - Self.tunnelsLock.lock() + Self.tunnelsLock.unlock() - if let index = Self.tunnels.firstIndex(where: { $0.identifier == self.identifier }) { - Self.tunnels.remove(at: index) - } else { - error = NEVPNError(.configurationReadWriteFailed) + completionHandler?(error) } - Self.tunnelsLock.unlock() - - completionHandler?(error) - } - - static func == (lhs: SimulatorTunnelProviderManager, rhs: SimulatorTunnelProviderManager) -> Bool { - lhs.identifier == rhs.identifier + static func == ( + lhs: SimulatorTunnelProviderManager, + rhs: SimulatorTunnelProviderManager + ) -> Bool { + lhs.identifier == rhs.identifier + } } -} - #endif diff --git a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift index 7df234d75c..9c3b2623a7 100644 --- a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift +++ b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift @@ -8,104 +8,109 @@ #if targetEnvironment(simulator) -import Foundation -import enum NetworkExtension.NEProviderStopReason -import Logging + import Foundation + import Logging + import enum NetworkExtension.NEProviderStopReason -class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { - private var selectorResult: RelaySelectorResult? + class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { + private var selectorResult: RelaySelectorResult? - private let providerLogger = Logger(label: "SimulatorTunnelProviderHost") - private let dispatchQueue = DispatchQueue(label: "SimulatorTunnelProviderHostQueue") + private let providerLogger = Logger(label: "SimulatorTunnelProviderHost") + private let dispatchQueue = DispatchQueue(label: "SimulatorTunnelProviderHostQueue") - override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { - dispatchQueue.async { - var selectorResult: RelaySelectorResult? + override func startTunnel( + options: [String: NSObject]?, + completionHandler: @escaping (Error?) -> Void + ) { + dispatchQueue.async { + var selectorResult: RelaySelectorResult? - do { - let tunnelOptions = PacketTunnelOptions(rawOptions: options ?? [:]) + do { + let tunnelOptions = PacketTunnelOptions(rawOptions: options ?? [:]) - selectorResult = try tunnelOptions.getSelectorResult() - } catch { - self.providerLogger.error( - chainedError: AnyChainedError(error), - message: """ - Failed to decode relay selector result passed from the app. \ - Will continue by picking new relay. - """ - ) - } + selectorResult = try tunnelOptions.getSelectorResult() + } catch { + self.providerLogger.error( + chainedError: AnyChainedError(error), + message: """ + Failed to decode relay selector result passed from the app. \ + Will continue by picking new relay. + """ + ) + } - do { - self.selectorResult = try selectorResult ?? self.pickRelay() + do { + self.selectorResult = try selectorResult ?? self.pickRelay() - completionHandler(nil) - } catch { - self.providerLogger.error( - chainedError: AnyChainedError(error), - message: "Failed to pick relay." - ) - completionHandler(error) + completionHandler(nil) + } catch { + self.providerLogger.error( + chainedError: AnyChainedError(error), + message: "Failed to pick relay." + ) + completionHandler(error) + } } } - } - override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - dispatchQueue.async { - self.selectorResult = nil + override func stopTunnel( + with reason: NEProviderStopReason, + completionHandler: @escaping () -> Void + ) { + dispatchQueue.async { + self.selectorResult = nil - completionHandler() + completionHandler() + } } - } - override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { - dispatchQueue.async { - do { - let response = try self.processMessage(messageData) + override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { + dispatchQueue.async { + do { + let response = try self.processMessage(messageData) - completionHandler?(response) - } catch { - self.providerLogger.error( - chainedError: AnyChainedError(error), - message: "Failed to handle app message." - ) + completionHandler?(response) + } catch { + self.providerLogger.error( + chainedError: AnyChainedError(error), + message: "Failed to handle app message." + ) - completionHandler?(nil) + completionHandler?(nil) + } } } - } - private func processMessage(_ messageData: Data) throws -> Data? { - let message = try TunnelProviderMessage(messageData: messageData) + private func processMessage(_ messageData: Data) throws -> Data? { + let message = try TunnelProviderMessage(messageData: messageData) - switch message { - case .getTunnelStatus: - var tunnelStatus = PacketTunnelStatus() - tunnelStatus.tunnelRelay = self.selectorResult?.packetTunnelRelay + switch message { + case .getTunnelStatus: + var tunnelStatus = PacketTunnelStatus() + tunnelStatus.tunnelRelay = self.selectorResult?.packetTunnelRelay - return try TunnelProviderReply(tunnelStatus).encode() + return try TunnelProviderReply(tunnelStatus).encode() - case .reconnectTunnel(let aSelectorResult): - reasserting = true - if let aSelectorResult = aSelectorResult { - selectorResult = aSelectorResult - } - reasserting = false + case let .reconnectTunnel(aSelectorResult): + reasserting = true + if let aSelectorResult = aSelectorResult { + selectorResult = aSelectorResult + } + reasserting = false - return nil + return nil + } } - } - private func pickRelay() throws -> RelaySelectorResult { - let cachedRelays = try RelayCache.Tracker.shared.getCachedRelays() - let tunnelSettings = try SettingsManager.readSettings() + private func pickRelay() throws -> RelaySelectorResult { + let cachedRelays = try RelayCache.Tracker.shared.getCachedRelays() + let tunnelSettings = try SettingsManager.readSettings() - return try RelaySelector.evaluate( - relays: cachedRelays.relays, - constraints: tunnelSettings.relayConstraints - ) + return try RelaySelector.evaluate( + relays: cachedRelays.relays, + constraints: tunnelSettings.relayConstraints + ) + } } -} - #endif diff --git a/ios/MullvadVPN/SpinnerActivityIndicatorView.swift b/ios/MullvadVPN/SpinnerActivityIndicatorView.swift index 4c7332ae6f..f09cd507e7 100644 --- a/ios/MullvadVPN/SpinnerActivityIndicatorView.swift +++ b/ios/MullvadVPN/SpinnerActivityIndicatorView.swift @@ -121,7 +121,8 @@ class SpinnerActivityIndicatorView: UIView { object: object, queue: .main, using: { [weak self] _ in self?.restartAnimationIfNeeded() - }) + } + ) } private func unregisterSceneActivationObserver() { @@ -134,7 +135,7 @@ class SpinnerActivityIndicatorView: UIView { private func restartAnimationIfNeeded() { let animation = layer.animation(forKey: Self.rotationAnimationKey) - if isAnimating && animation == nil { + if isAnimating, animation == nil { removeAnimation() addAnimation() } diff --git a/ios/MullvadVPN/StatusImageView.swift b/ios/MullvadVPN/StatusImageView.swift index 3507a833d3..af0ef14ae7 100644 --- a/ios/MullvadVPN/StatusImageView.swift +++ b/ios/MullvadVPN/StatusImageView.swift @@ -35,13 +35,13 @@ class StatusImageView: UIImageView { override init(frame: CGRect) { super.init(frame: frame) - self.image = style.image + image = style.image } init(style: Style) { self.style = style super.init(image: style.image) - self.image = style.image + image = style.image } required init?(coder: NSCoder) { diff --git a/ios/MullvadVPN/String+Split.swift b/ios/MullvadVPN/String+Split.swift index 82352280fc..f62317343b 100644 --- a/ios/MullvadVPN/String+Split.swift +++ b/ios/MullvadVPN/String+Split.swift @@ -9,7 +9,6 @@ import Foundation extension String { - /// Returns the array of the longest possible subsequences of the given length. func split(every length: Int) -> [Substring] { guard length > 0 else { return [prefix(upTo: endIndex)] } diff --git a/ios/MullvadVPN/Swizzle.swift b/ios/MullvadVPN/Swizzle.swift index 994285ce56..6073384cf4 100644 --- a/ios/MullvadVPN/Swizzle.swift +++ b/ios/MullvadVPN/Swizzle.swift @@ -12,8 +12,18 @@ import Foundation guard let originalMethod = class_getInstanceMethod(aClass, originalSelector), let newMethod = class_getInstanceMethod(aClass, newSelector) else { return } - if class_addMethod(aClass, originalSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)) { - class_replaceMethod(aClass, newSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) + if class_addMethod( + aClass, + originalSelector, + method_getImplementation(newMethod), + method_getTypeEncoding(newMethod) + ) { + class_replaceMethod( + aClass, + newSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod) + ) } else { method_exchangeImplementations(originalMethod, newMethod) } diff --git a/ios/MullvadVPN/TermsOfServiceContentView.swift b/ios/MullvadVPN/TermsOfServiceContentView.swift index 1f2740bbfb..58cf8b590e 100644 --- a/ios/MullvadVPN/TermsOfServiceContentView.swift +++ b/ios/MullvadVPN/TermsOfServiceContentView.swift @@ -9,7 +9,6 @@ import UIKit class TermsOfServiceContentView: UIView { - let titleLabel: UILabel = { let titleLabel = UILabel() titleLabel.translatesAutoresizingMaskIntoConstraints = false @@ -133,24 +132,37 @@ class TermsOfServiceContentView: UIView { footerContainer.bottomAnchor.constraint(equalTo: bottomAnchor), agreeButton.topAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.topAnchor), - agreeButton.leadingAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.leadingAnchor), - agreeButton.trailingAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.trailingAnchor), - agreeButton.bottomAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.bottomAnchor), + agreeButton.leadingAnchor + .constraint(equalTo: footerContainer.layoutMarginsGuide.leadingAnchor), + agreeButton.trailingAnchor + .constraint(equalTo: footerContainer.layoutMarginsGuide.trailingAnchor), + agreeButton.bottomAnchor + .constraint(equalTo: footerContainer.layoutMarginsGuide.bottomAnchor), - titleLabel.topAnchor.constraint(equalTo: scrollContentContainer.layoutMarginsGuide.topAnchor), - titleLabel.leadingAnchor.constraint(equalTo: scrollContentContainer.layoutMarginsGuide.leadingAnchor), - titleLabel.trailingAnchor.constraint(equalTo: scrollContentContainer.layoutMarginsGuide.trailingAnchor), + titleLabel.topAnchor + .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.topAnchor), + titleLabel.leadingAnchor + .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.leadingAnchor), + titleLabel.trailingAnchor + .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.trailingAnchor), bodyLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 24), - bodyLabel.leadingAnchor.constraint(equalTo: scrollContentContainer.layoutMarginsGuide.leadingAnchor), - bodyLabel.trailingAnchor.constraint(equalTo: scrollContentContainer.layoutMarginsGuide.trailingAnchor), + bodyLabel.leadingAnchor + .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.leadingAnchor), + bodyLabel.trailingAnchor + .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.trailingAnchor), privacyPolicyLink.topAnchor.constraint(equalTo: bodyLabel.bottomAnchor, constant: 24), - privacyPolicyLink.leadingAnchor.constraint(equalTo: scrollContentContainer.layoutMarginsGuide.leadingAnchor), - privacyPolicyLink.trailingAnchor.constraint(lessThanOrEqualTo: scrollContentContainer.layoutMarginsGuide.trailingAnchor), - privacyPolicyLink.bottomAnchor.constraint(equalTo: scrollContentContainer.layoutMarginsGuide.bottomAnchor), + privacyPolicyLink.leadingAnchor + .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.leadingAnchor), + privacyPolicyLink.trailingAnchor + .constraint( + lessThanOrEqualTo: scrollContentContainer.layoutMarginsGuide + .trailingAnchor + ), + privacyPolicyLink.bottomAnchor + .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.bottomAnchor), ]) } - } diff --git a/ios/MullvadVPN/TermsOfServiceViewController.swift b/ios/MullvadVPN/TermsOfServiceViewController.swift index dbc04555dc..3dd909fa06 100644 --- a/ios/MullvadVPN/TermsOfServiceViewController.swift +++ b/ios/MullvadVPN/TermsOfServiceViewController.swift @@ -9,8 +9,9 @@ import SafariServices import UIKit -class TermsOfServiceViewController: UIViewController, RootContainment, SFSafariViewControllerDelegate { - +class TermsOfServiceViewController: UIViewController, RootContainment, + SFSafariViewControllerDelegate +{ var completionHandler: ((UIViewController) -> Void)? override var preferredStatusBarStyle: UIStatusBarStyle { @@ -32,8 +33,16 @@ class TermsOfServiceViewController: UIViewController, RootContainment, SFSafariV let contentView = TermsOfServiceContentView() contentView.translatesAutoresizingMaskIntoConstraints = false - contentView.agreeButton.addTarget(self, action: #selector(handleAgreeButton(_:)), for: .touchUpInside) - contentView.privacyPolicyLink.addTarget(self, action: #selector(handlePrivacyPolicyButton(_:)), for: .touchUpInside) + contentView.agreeButton.addTarget( + self, + action: #selector(handleAgreeButton(_:)), + for: .touchUpInside + ) + contentView.privacyPolicyLink.addTarget( + self, + action: #selector(handlePrivacyPolicyButton(_:)), + for: .touchUpInside + ) view.backgroundColor = .primaryColor view.addSubview(contentView) @@ -49,7 +58,10 @@ class TermsOfServiceViewController: UIViewController, RootContainment, SFSafariV // MARK: - Actions @objc private func handlePrivacyPolicyButton(_ sender: Any) { - let safariController = SFSafariViewController(url: ApplicationConfiguration.privacyPolicyURL) + let safariController = SFSafariViewController( + url: ApplicationConfiguration + .privacyPolicyURL + ) safariController.delegate = self present(safariController, animated: true) diff --git a/ios/MullvadVPN/TranslucentButtonBlurView.swift b/ios/MullvadVPN/TranslucentButtonBlurView.swift index 78c916d5ec..d10e606baf 100644 --- a/ios/MullvadVPN/TranslucentButtonBlurView.swift +++ b/ios/MullvadVPN/TranslucentButtonBlurView.swift @@ -22,7 +22,7 @@ class TranslucentButtonBlurView: UIVisualEffectView { button.topAnchor.constraint(equalTo: contentView.topAnchor), button.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), button.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - button.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + button.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), ]) layer.cornerRadius = UIMetrics.controlCornerRadius @@ -36,16 +36,20 @@ class TranslucentButtonBlurView: UIVisualEffectView { } private extension AppButton.Style { - func cornerMask(_ userInterfaceLayoutDirection: UIUserInterfaceLayoutDirection) -> CACornerMask { + func cornerMask(_ userInterfaceLayoutDirection: UIUserInterfaceLayoutDirection) + -> CACornerMask + { switch (self, userInterfaceLayoutDirection) { - case (.translucentDangerSplitLeft, .leftToRight), (.translucentDangerSplitRight, .rightToLeft): + case (.translucentDangerSplitLeft, .leftToRight), + (.translucentDangerSplitRight, .rightToLeft): return [.layerMinXMinYCorner, .layerMinXMaxYCorner] - case (.translucentDangerSplitRight, .leftToRight), (.translucentDangerSplitLeft, .rightToLeft): + case (.translucentDangerSplitRight, .leftToRight), + (.translucentDangerSplitLeft, .rightToLeft): return [.layerMaxXMinYCorner, .layerMaxXMaxYCorner] default: return [ .layerMinXMinYCorner, .layerMinXMaxYCorner, - .layerMaxXMinYCorner, .layerMaxXMaxYCorner + .layerMaxXMinYCorner, .layerMaxXMaxYCorner, ] } } diff --git a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift index 0fc5818d59..e98aaff5fd 100644 --- a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift +++ b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift @@ -9,7 +9,7 @@ import Foundation import Logging -class LoadTunnelConfigurationOperation: ResultOperation<(), Error> { +class LoadTunnelConfigurationOperation: ResultOperation<Void, Error> { private let logger = Logger(label: "LoadTunnelConfigurationOperation") private let interactor: TunnelInteractor @@ -61,6 +61,7 @@ class LoadTunnelConfigurationOperation: ResultOperation<(), Error> { finishOperation(tunnel: tunnel) } } + private func finishOperation(tunnel: Tunnel?) { interactor.setTunnel(tunnel, shouldRefreshTunnelState: true) interactor.setConfigurationLoaded() diff --git a/ios/MullvadVPN/TunnelManager/LoadTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/LoadTunnelOperation.swift index 7f9bec8515..39d474e38c 100644 --- a/ios/MullvadVPN/TunnelManager/LoadTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/LoadTunnelOperation.swift @@ -9,13 +9,18 @@ import Foundation import Logging -class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { +class LoadTunnelOperation: ResultOperation<Void, TunnelManager.Error> { private let accountToken: String? private let state: TunnelManager.State private let logger = Logger(label: "TunnelManager.LoadTunnelOperation") - init(dispatchQueue: DispatchQueue, state: TunnelManager.State, accountToken: String?, completionHandler: @escaping CompletionHandler) { + init( + dispatchQueue: DispatchQueue, + state: TunnelManager.State, + accountToken: String?, + completionHandler: @escaping CompletionHandler + ) { self.state = state self.accountToken = accountToken @@ -37,7 +42,7 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { if let accountToken = accountToken { let migrationResult = migrateTunnelSettings(accountToken: accountToken) - if case .failure(let migrationError) = migrationResult { + if case let .failure(migrationError) = migrationResult { completionHandler(.failure(migrationError)) return } @@ -48,19 +53,29 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { if let error = error { completionHandler(.failure(.loadAllVPNConfigurations(error))) } else { - self.didLoadVPNConfigurations(tunnels: tunnels, completionHandler: completionHandler) + self.didLoadVPNConfigurations( + tunnels: tunnels, + completionHandler: completionHandler + ) } } } } - private func didLoadVPNConfigurations(tunnels: [TunnelProviderManagerType]?, completionHandler: @escaping CompletionHandler) { + private func didLoadVPNConfigurations( + tunnels: [TunnelProviderManagerType]?, + completionHandler: @escaping CompletionHandler + ) { if let tunnelProvider = tunnels?.first { if let accountToken = accountToken { // Case 1: tunnel exists and account token is set. // Verify that tunnel can access the configuration via the persistent keychain reference // stored in `passwordReference` field of VPN configuration. - handleTunnelConsistency(tunnelProvider: tunnelProvider, accountToken: accountToken, completionHandler: completionHandler) + handleTunnelConsistency( + tunnelProvider: tunnelProvider, + accountToken: accountToken, + completionHandler: completionHandler + ) } else { // Case 2: tunnel exists but account token is unset. // Remove the orphaned tunnel. @@ -78,10 +93,11 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { if let accountToken = accountToken { // Case 3: tunnel does not exist but the account token is set. // Verify that tunnel settings exists in keychain. - let tunnelSettingsResult = TunnelSettingsManager.load(searchTerm: .accountToken(accountToken)) - .mapError { TunnelManager.Error.readTunnelSettings($0) } + let tunnelSettingsResult = TunnelSettingsManager + .load(searchTerm: .accountToken(accountToken)) + .mapError { TunnelManager.Error.readTunnelSettings($0) } - if case .success(let keychainEntry) = tunnelSettingsResult { + if case let .success(keychainEntry) = tunnelSettingsResult { let tunnelInfo = TunnelInfo( token: keychainEntry.accountToken, tunnelSettings: keychainEntry.tunnelSettings @@ -98,14 +114,25 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { } } - private func handleTunnelConsistency(tunnelProvider: TunnelProviderManagerType, accountToken: String, completionHandler: @escaping CompletionHandler) { - let verificationResult = verifyTunnel(tunnelProvider: tunnelProvider, expectedAccountToken: accountToken) - let tunnelSettingsResult = TunnelSettingsManager.load(searchTerm: .accountToken(accountToken)) + private func handleTunnelConsistency( + tunnelProvider: TunnelProviderManagerType, + accountToken: String, + completionHandler: @escaping CompletionHandler + ) { + let verificationResult = verifyTunnel( + tunnelProvider: tunnelProvider, + expectedAccountToken: accountToken + ) + let tunnelSettingsResult = TunnelSettingsManager + .load(searchTerm: .accountToken(accountToken)) .mapError { TunnelManager.Error.readTunnelSettings($0) } switch (verificationResult, tunnelSettingsResult) { - case (.success(true), .success(let keychainEntry)): - let tunnelInfo = TunnelInfo(token: accountToken, tunnelSettings: keychainEntry.tunnelSettings) + case (.success(true), let .success(keychainEntry)): + let tunnelInfo = TunnelInfo( + token: accountToken, + tunnelSettings: keychainEntry.tunnelSettings + ) state.tunnelInfo = tunnelInfo state.setTunnel(Tunnel(tunnelProvider: tunnelProvider), shouldRefreshTunnelState: true) @@ -114,13 +141,16 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { // Remove the tunnel with corrupt configuration. // It will be re-created upon the first attempt to connect the tunnel. - case (.success(false), .success(let keychainEntry)): + case (.success(false), let .success(keychainEntry)): tunnelProvider.removeFromPreferences { error in self.dispatchQueue.async { if let error = error { completionHandler(.failure(.removeInconsistentVPNConfiguration(error))) } else { - let tunnelInfo = TunnelInfo(token: accountToken, tunnelSettings: keychainEntry.tunnelSettings) + let tunnelInfo = TunnelInfo( + token: accountToken, + tunnelSettings: keychainEntry.tunnelSettings + ) self.state.tunnelInfo = tunnelInfo completionHandler(.success(())) @@ -130,8 +160,11 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { // Remove the tunnel when failed to verify it but successfuly loaded the tunnel // settings. - case (.failure(let verificationError), .success(let keychainEntry)): - logger.error(chainedError: verificationError, message: "Failed to verify the tunnel but successfully loaded the tunnel settings. Removing the tunnel.") + case let (.failure(verificationError), .success(keychainEntry)): + logger.error( + chainedError: verificationError, + message: "Failed to verify the tunnel but successfully loaded the tunnel settings. Removing the tunnel." + ) // Remove the tunnel with corrupt configuration. // It will be re-created upon the first attempt to connect the tunnel. @@ -140,7 +173,10 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { if let error = error { completionHandler(.failure(.removeInconsistentVPNConfiguration(error))) } else { - let tunnelInfo = TunnelInfo(token: accountToken, tunnelSettings: keychainEntry.tunnelSettings) + let tunnelInfo = TunnelInfo( + token: accountToken, + tunnelSettings: keychainEntry.tunnelSettings + ) self.state.tunnelInfo = tunnelInfo completionHandler(.success(())) @@ -149,8 +185,11 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { } // Remove the tunnel when failed to verify the tunnel and load tunnel settings. - case (.failure(let verificationError), .failure(_)): - logger.error(chainedError: verificationError, message: "Failed to verify the tunnel and load tunnel settings. Removing the tunnel.") + case let (.failure(verificationError), .failure(_)): + logger.error( + chainedError: verificationError, + message: "Failed to verify the tunnel and load tunnel settings. Removing the tunnel." + ) tunnelProvider.removeFromPreferences { error in self.dispatchQueue.async { @@ -163,8 +202,11 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { } // Remove the tunnel when the app is not able to read tunnel settings - case (.success(_), .failure(let settingsReadError)): - logger.error(chainedError: settingsReadError, message: "Failed to load tunnel settings. Removing the tunnel.") + case let (.success(_), .failure(settingsReadError)): + logger.error( + chainedError: settingsReadError, + message: "Failed to load tunnel settings. Removing the tunnel." + ) tunnelProvider.removeFromPreferences { error in self.dispatchQueue.async { @@ -178,10 +220,18 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { } } - private func verifyTunnel(tunnelProvider: TunnelProviderManagerType, expectedAccountToken accountToken: String) -> Result<Bool, TunnelManager.Error> { + private func verifyTunnel( + tunnelProvider: TunnelProviderManagerType, + expectedAccountToken accountToken: String + ) -> Result<Bool, TunnelManager.Error> { // Check that the VPN configuration points to the same account token - guard let username = tunnelProvider.protocolConfiguration?.username, username == accountToken else { - logger.warning("The token assigned to the VPN configuration does not match the logged in account.") + guard let username = tunnelProvider.protocolConfiguration?.username, + username == accountToken + else { + logger + .warning( + "The token assigned to the VPN configuration does not match the logged in account." + ) return .success(false) } @@ -195,8 +245,11 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { // Verify that the keychain reference points to the existing entry in Keychain. // Bad reference is possible when migrating the user data from one device to the other. return TunnelSettingsManager.exists(searchTerm: .persistentReference(keychainReference)) - .mapError { (error) -> TunnelManager.Error in - logger.error(chainedError: error, message: "Failed to verify the persistent keychain reference for tunnel settings.") + .mapError { error -> TunnelManager.Error in + logger.error( + chainedError: error, + message: "Failed to verify the persistent keychain reference for tunnel settings." + ) return .readTunnelSettings(error) } @@ -205,19 +258,19 @@ class LoadTunnelOperation: ResultOperation<(), TunnelManager.Error> { private func migrateTunnelSettings(accountToken: String) -> Result<Bool, TunnelManager.Error> { let result = TunnelSettingsManager .migrateKeychainEntry(searchTerm: .accountToken(accountToken)) - .mapError { (error) -> TunnelManager.Error in + .mapError { error -> TunnelManager.Error in return .migrateTunnelSettings(error) } switch result { - case .success(let migrated): + case let .success(migrated): if migrated { logger.info("Migrated Keychain tunnel configuration.") } else { logger.info("Tunnel settings are up to date. No migration needed.") } - case .failure(let error): + case let .failure(error): logger.error(chainedError: error) } diff --git a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift index 873968521b..c34e2fc8c0 100644 --- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift +++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift @@ -7,8 +7,8 @@ // import Foundation -import NetworkExtension import Logging +import NetworkExtension class MapConnectionStatusOperation: AsyncOperation { private let interactor: TunnelInteractor @@ -21,8 +21,7 @@ class MapConnectionStatusOperation: AsyncOperation { queue: DispatchQueue, interactor: TunnelInteractor, connectionStatus: NEVPNStatus - ) - { + ) { self.interactor = interactor self.connectionStatus = connectionStatus @@ -103,13 +102,12 @@ class MapConnectionStatusOperation: AsyncOperation { private func updateTunnelRelayAndFinish( tunnel: Tunnel, mapRelayToState: @escaping (PacketTunnelRelay?) -> TunnelState? - ) - { + ) { request = tunnel.getTunnelStatus { [weak self] completion in guard let self = self else { return } self.dispatchQueue.async { - if case .success(let packetTunnelStatus) = completion, !self.isCancelled { + if case let .success(packetTunnelStatus) = completion, !self.isCancelled { self.interactor.updateTunnelStatus( from: packetTunnelStatus, mappingRelayToState: mapRelayToState diff --git a/ios/MullvadVPN/TunnelManager/MigrateSettingsOperation.swift b/ios/MullvadVPN/TunnelManager/MigrateSettingsOperation.swift index d67f8e37e8..17fc69cff1 100644 --- a/ios/MullvadVPN/TunnelManager/MigrateSettingsOperation.swift +++ b/ios/MullvadVPN/TunnelManager/MigrateSettingsOperation.swift @@ -29,8 +29,7 @@ class MigrateSettingsOperation: AsyncOperation { dispatchQueue: DispatchQueue, accountsProxy: REST.AccountsProxy, devicesProxy: REST.DevicesProxy - ) - { + ) { self.accountsProxy = accountsProxy self.devicesProxy = devicesProxy @@ -139,12 +138,15 @@ class MigrateSettingsOperation: AsyncOperation { } } - private func didFinishAccountRequest(_ completion: OperationCompletion<REST.AccountData, REST.Error>) { + private func didFinishAccountRequest(_ completion: OperationCompletion< + REST.AccountData, + REST.Error + >) { switch completion { - case .success(let accountData): + case let .success(accountData): self.accountData = accountData - case .failure(let error): + case let .failure(error): logger.error(chainedError: error, message: "Failed to fetch accound data.") case .cancelled: @@ -152,12 +154,15 @@ class MigrateSettingsOperation: AsyncOperation { } } - private func didFinishDeviceRequest(_ completion: OperationCompletion<[REST.Device], REST.Error>) { + private func didFinishDeviceRequest(_ completion: OperationCompletion< + [REST.Device], + REST.Error + >) { switch completion { - case .success(let devices): + case let .success(devices): self.devices = devices - case .failure(let error): + case let .failure(error): logger.error(chainedError: error, message: "Failed to fetch devices.") case .cancelled: @@ -176,7 +181,7 @@ class MigrateSettingsOperation: AsyncOperation { // Find device that matches the public key stored in legacy settings. let device = devices.first { device in return device.pubkey == interfaceData.privateKey.publicKey || - device.pubkey == interfaceData.nextPrivateKey?.publicKey + device.pubkey == interfaceData.nextPrivateKey?.publicKey } guard let device = device else { @@ -190,8 +195,7 @@ class MigrateSettingsOperation: AsyncOperation { // Match private key. let privateKeyWithMetadata: PrivateKeyWithMetadata - if let nextKey = interfaceData.nextPrivateKey, nextKey.publicKey == device.pubkey - { + if let nextKey = interfaceData.nextPrivateKey, nextKey.publicKey == device.pubkey { privateKeyWithMetadata = nextKey } else { privateKeyWithMetadata = interfaceData.privateKey @@ -249,5 +253,4 @@ class MigrateSettingsOperation: AsyncOperation { finish() } - } diff --git a/ios/MullvadVPN/TunnelManager/PacketTunnelOptions.swift b/ios/MullvadVPN/TunnelManager/PacketTunnelOptions.swift index ca8b0ecb50..d6a3578bc5 100644 --- a/ios/MullvadVPN/TunnelManager/PacketTunnelOptions.swift +++ b/ios/MullvadVPN/TunnelManager/PacketTunnelOptions.swift @@ -9,7 +9,6 @@ import Foundation struct PacketTunnelOptions { - /// Keys for options dictionary private enum Keys: String { /// Option key that holds the `NSData` value with `RelaySelectorResult` encoded using `JSONEncoder`. diff --git a/ios/MullvadVPN/TunnelManager/PacketTunnelStatus.swift b/ios/MullvadVPN/TunnelManager/PacketTunnelStatus.swift index c7ffbb2191..9283e37cb8 100644 --- a/ios/MullvadVPN/TunnelManager/PacketTunnelStatus.swift +++ b/ios/MullvadVPN/TunnelManager/PacketTunnelStatus.swift @@ -11,7 +11,7 @@ import Foundation /// Struct describing packet tunnel process status. struct PacketTunnelStatus: Codable, Equatable { /// Flag indicating whether network is reachable. - var isNetworkReachable: Bool = true + var isNetworkReachable = true /// When the packet tunnel started connecting. var connectingDate: Date? diff --git a/ios/MullvadVPN/TunnelManager/ReconnectTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/ReconnectTunnelOperation.swift index e48d17482a..495e5f0479 100644 --- a/ios/MullvadVPN/TunnelManager/ReconnectTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/ReconnectTunnelOperation.swift @@ -8,7 +8,7 @@ import Foundation -class ReconnectTunnelOperation: ResultOperation<(), Error> { +class ReconnectTunnelOperation: ResultOperation<Void, Error> { private let interactor: TunnelInteractor private let selectNewRelay: Bool private var task: Cancellable? @@ -17,8 +17,7 @@ class ReconnectTunnelOperation: ResultOperation<(), Error> { dispatchQueue: DispatchQueue, interactor: TunnelInteractor, selectNewRelay: Bool - ) - { + ) { self.interactor = interactor self.selectNewRelay = selectNewRelay diff --git a/ios/MullvadVPN/TunnelManager/RegenerateTunnelPrivateKeyOperation.swift b/ios/MullvadVPN/TunnelManager/RegenerateTunnelPrivateKeyOperation.swift index 8d26fa6b05..14c7245908 100644 --- a/ios/MullvadVPN/TunnelManager/RegenerateTunnelPrivateKeyOperation.swift +++ b/ios/MullvadVPN/TunnelManager/RegenerateTunnelPrivateKeyOperation.swift @@ -9,7 +9,7 @@ import Foundation class RegeneratePrivateKeyOperation: AsyncOperation { - typealias CompletionHandler = (OperationCompletion<(), TunnelManager.Error>) -> Void + typealias CompletionHandler = (OperationCompletion<Void, TunnelManager.Error>) -> Void private let queue: DispatchQueue private let state: TunnelManager.State @@ -17,7 +17,12 @@ class RegeneratePrivateKeyOperation: AsyncOperation { private var completionHandler: CompletionHandler? private var restRequest: Cancellable? - init(queue: DispatchQueue, state: TunnelManager.State, restClient: REST.Client, completionHandler: @escaping CompletionHandler) { + init( + queue: DispatchQueue, + state: TunnelManager.State, + restClient: REST.Client, + completionHandler: @escaping CompletionHandler + ) { self.queue = queue self.state = state self.restClient = restClient @@ -46,7 +51,7 @@ class RegeneratePrivateKeyOperation: AsyncOperation { } private func execute(completionHandler: @escaping CompletionHandler) { - guard !self.isCancelled else { + guard !isCancelled else { completionHandler(.cancelled) return } @@ -59,7 +64,7 @@ class RegeneratePrivateKeyOperation: AsyncOperation { let newPrivateKey = PrivateKeyWithMetadata() let oldPublicKey = tunnelInfo.tunnelSettings.interface.publicKey - let restRequestAdapter = self.restClient.replaceWireguardKey( + let restRequestAdapter = restClient.replaceWireguardKey( token: tunnelInfo.token, oldPublicKey: oldPublicKey, newPublicKey: newPrivateKey.publicKey @@ -67,9 +72,13 @@ class RegeneratePrivateKeyOperation: AsyncOperation { restRequest = restRequestAdapter.execute(retryStrategy: .default) { restResult in self.queue.async { - let saveResult = Self.handleResponse(accountToken: tunnelInfo.token, newPrivateKey: newPrivateKey, result: restResult) + let saveResult = Self.handleResponse( + accountToken: tunnelInfo.token, + newPrivateKey: newPrivateKey, + result: restResult + ) - if case .success(let newTunnelSettings) = saveResult { + if case let .success(newTunnelSettings) = saveResult { self.state.tunnelInfo?.tunnelSettings = newTunnelSettings } @@ -78,21 +87,25 @@ class RegeneratePrivateKeyOperation: AsyncOperation { } } - private class func handleResponse(accountToken: String, newPrivateKey: PrivateKeyWithMetadata, result: Result<REST.WireguardAddressesResponse, REST.Error>) -> Result<TunnelSettings, TunnelManager.Error> { + private class func handleResponse( + accountToken: String, + newPrivateKey: PrivateKeyWithMetadata, + result: Result<REST.WireguardAddressesResponse, REST.Error> + ) -> Result<TunnelSettings, TunnelManager.Error> { return result.flatMapError { restError in return .failure(.replaceWireguardKey(restError)) } .flatMap { associatedAddresses in - return TunnelSettingsManager.update(searchTerm: .accountToken(accountToken)) { newTunnelSettings in - newTunnelSettings.interface.privateKey = newPrivateKey - newTunnelSettings.interface.addresses = [ - associatedAddresses.ipv4Address, - associatedAddresses.ipv6Address - ] - }.mapError { error -> TunnelManager.Error in - return .updateTunnelSettings(error) - } + return TunnelSettingsManager + .update(searchTerm: .accountToken(accountToken)) { newTunnelSettings in + newTunnelSettings.interface.privateKey = newPrivateKey + newTunnelSettings.interface.addresses = [ + associatedAddresses.ipv4Address, + associatedAddresses.ipv6Address, + ] + }.mapError { error -> TunnelManager.Error in + return .updateTunnelSettings(error) + } } } - } diff --git a/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift b/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift index ae5470b75e..5cdf10a872 100644 --- a/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift +++ b/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift @@ -37,7 +37,7 @@ class RotateKeyOperation: ResultOperation<Bool, Error> { } override func main() { - guard case .loggedIn(let accountData, let deviceData) = interactor.deviceState else { + guard case let .loggedIn(accountData, deviceData) = interactor.deviceState else { finish(completion: .failure(InvalidDeviceStateError())) return } @@ -85,10 +85,9 @@ class RotateKeyOperation: ResultOperation<Bool, Error> { private func didRotateKey( newPrivateKey: PrivateKey, completion: OperationCompletion<REST.Device, REST.Error> - ) - { + ) { switch completion { - case .success(let device): + case let .success(device): logger.debug("Successfully rotated device key. Persisting settings...") switch interactor.deviceState { @@ -106,7 +105,7 @@ class RotateKeyOperation: ResultOperation<Bool, Error> { finish(completion: .failure(InvalidDeviceStateError())) } - case .failure(let error): + case let .failure(error): logger.error( chainedError: AnyChainedError(error), message: "Failed to rotate device key." @@ -116,6 +115,5 @@ class RotateKeyOperation: ResultOperation<Bool, Error> { case .cancelled: finish(completion: .cancelled) } - } } diff --git a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift index 85227c5874..2652433cee 100644 --- a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift +++ b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift @@ -101,7 +101,7 @@ final class SendTunnelProviderMessageOperation<Output>: ResultOperation<Output, } private func handleVPNStatus(_ status: NEVPNStatus) { - guard !isCancelled && !messageSent else { + guard !isCancelled, !messageSent else { return } @@ -201,8 +201,7 @@ extension SendTunnelProviderMessageOperation where Output: Codable { tunnel: Tunnel, message: TunnelProviderMessage, completionHandler: @escaping CompletionHandler - ) - { + ) { self.init( dispatchQueue: dispatchQueue, tunnel: tunnel, @@ -248,7 +247,7 @@ enum SendTunnelProviderMessageError: ChainedError { var errorDescription: String? { switch self { - case .tunnelDown(let status): + case let .tunnelDown(status): return "Tunnel is either down or about to go down (status: \(status))." case .timeout: return "Send timeout." diff --git a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift index 022264489d..12d7f9b3a5 100644 --- a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift +++ b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift @@ -7,9 +7,9 @@ // import Foundation -import class WireGuardKitTypes.PublicKey -import class WireGuardKitTypes.PrivateKey import Logging +import class WireGuardKitTypes.PrivateKey +import class WireGuardKitTypes.PublicKey enum SetAccountAction { /// Set new account. @@ -47,9 +47,10 @@ private struct SetAccountContext: OperationInputContext { func reduce() -> SetAccountResult? { guard let accountData = accountData, let privateKey = privateKey, - let device = device else { - return nil - } + let device = device + else { + return nil + } return SetAccountResult( accountData: accountData, @@ -76,8 +77,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?, Error> { accountsProxy: REST.AccountsProxy, devicesProxy: REST.DevicesProxy, action: SetAccountAction - ) - { + ) { self.interactor = interactor self.accountsProxy = accountsProxy self.devicesProxy = devicesProxy @@ -88,7 +88,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?, Error> { override func main() { var deleteDeviceOperation: AsyncOperation? - if case .loggedIn(let accountData, let deviceData) = interactor.deviceState { + if case let .loggedIn(accountData, deviceData) = interactor.deviceState { let operation = getDeleteDeviceOperation( accounNumber: accountData.number, deviceIdentifier: deviceData.identifier @@ -187,7 +187,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?, Error> { case .new: return getCreateAccountOperation() - case .existing(let accountNumber): + case let .existing(accountNumber): return getExistingAccountOperation(accountNumber: accountNumber) case .unset: diff --git a/ios/MullvadVPN/TunnelManager/SetTunnelSettingsOperation.swift b/ios/MullvadVPN/TunnelManager/SetTunnelSettingsOperation.swift index d1761a215a..28c0731a4c 100644 --- a/ios/MullvadVPN/TunnelManager/SetTunnelSettingsOperation.swift +++ b/ios/MullvadVPN/TunnelManager/SetTunnelSettingsOperation.swift @@ -8,13 +8,18 @@ import Foundation -class SetTunnelSettingsOperation: ResultOperation<(), TunnelManager.Error> { +class SetTunnelSettingsOperation: ResultOperation<Void, TunnelManager.Error> { typealias ModificationHandler = (inout TunnelSettings) -> Void private let state: TunnelManager.State private let modificationBlock: ModificationHandler - init(dispatchQueue: DispatchQueue, state: TunnelManager.State, modificationBlock: @escaping ModificationHandler, completionHandler: @escaping CompletionHandler) { + init( + dispatchQueue: DispatchQueue, + state: TunnelManager.State, + modificationBlock: @escaping ModificationHandler, + completionHandler: @escaping CompletionHandler + ) { self.state = state self.modificationBlock = modificationBlock @@ -31,16 +36,17 @@ class SetTunnelSettingsOperation: ResultOperation<(), TunnelManager.Error> { return } - let result = TunnelSettingsManager.update(searchTerm: .accountToken(accountToken)) { tunnelSettings in - modificationBlock(&tunnelSettings) - } + let result = TunnelSettingsManager + .update(searchTerm: .accountToken(accountToken)) { tunnelSettings in + modificationBlock(&tunnelSettings) + } switch result { - case .success(let newTunnelSettings): + case let .success(newTunnelSettings): state.tunnelInfo?.tunnelSettings = newTunnelSettings finish(completion: .success(())) - case .failure(let error): + case let .failure(error): finish(completion: .failure(.updateTunnelSettings(error))) } } diff --git a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift index cb3a738490..f30867bc97 100644 --- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift @@ -7,10 +7,10 @@ // import Foundation -import NetworkExtension import Logging +import NetworkExtension -class StartTunnelOperation: ResultOperation<(), Error> { +class StartTunnelOperation: ResultOperation<Void, Error> { typealias EncodeErrorHandler = (Error) -> Void private let interactor: TunnelInteractor @@ -20,8 +20,7 @@ class StartTunnelOperation: ResultOperation<(), Error> { dispatchQueue: DispatchQueue, interactor: TunnelInteractor, completionHandler: @escaping CompletionHandler - ) - { + ) { self.interactor = interactor super.init( @@ -100,13 +99,21 @@ class StartTunnelOperation: ResultOperation<(), Error> { ) } - interactor.setTunnel(Tunnel(tunnelProvider: tunnelProvider), shouldRefreshTunnelState: false) + interactor.setTunnel( + Tunnel(tunnelProvider: tunnelProvider), + shouldRefreshTunnelState: false + ) interactor.resetTunnelState(to: .connecting(selectorResult.packetTunnelRelay)) try tunnelProvider.connection.startVPNTunnel(options: tunnelOptions.rawOptions()) } - private class func makeTunnelProvider(completionHandler: @escaping (Result<TunnelProviderManagerType, Error>) -> Void) { + private class func makeTunnelProvider( + completionHandler: @escaping (Result< + TunnelProviderManagerType, + Error + >) -> Void + ) { TunnelProviderManagerType.loadAllFromPreferences { tunnelProviders, error in if let error = error { completionHandler(.failure(error)) @@ -135,7 +142,8 @@ class StartTunnelOperation: ResultOperation<(), Error> { private class func configureTunnelProvider(_ tunnelProvider: TunnelProviderManagerType) { let protocolConfig = NETunnelProviderProtocol() - protocolConfig.providerBundleIdentifier = ApplicationConfiguration.packetTunnelExtensionIdentifier + protocolConfig.providerBundleIdentifier = ApplicationConfiguration + .packetTunnelExtensionIdentifier protocolConfig.serverAddress = "" tunnelProvider.isEnabled = true diff --git a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift index bd0501f114..00814f024b 100644 --- a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift @@ -8,15 +8,14 @@ import Foundation -class StopTunnelOperation: ResultOperation<(), Error> { +class StopTunnelOperation: ResultOperation<Void, Error> { private let interactor: TunnelInteractor init( dispatchQueue: DispatchQueue, interactor: TunnelInteractor, completionHandler: @escaping CompletionHandler - ) - { + ) { self.interactor = interactor super.init( diff --git a/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift b/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift index b773a54b1a..ef737466f3 100644 --- a/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift +++ b/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift @@ -36,8 +36,7 @@ extension Tunnel { /// Request status from packet tunnel process. func getTunnelStatus( completionHandler: @escaping (OperationCompletion<PacketTunnelStatus, Error>) -> Void - ) -> Cancellable - { + ) -> Cancellable { let operation = SendTunnelProviderMessageOperation( dispatchQueue: dispatchQueue, tunnel: self, diff --git a/ios/MullvadVPN/TunnelManager/Tunnel.swift b/ios/MullvadVPN/TunnelManager/Tunnel.swift index 93477eb403..682b969485 100644 --- a/ios/MullvadVPN/TunnelManager/Tunnel.swift +++ b/ios/MullvadVPN/TunnelManager/Tunnel.swift @@ -11,9 +11,9 @@ import NetworkExtension // Switch to stabs on simulator #if targetEnvironment(simulator) -typealias TunnelProviderManagerType = SimulatorTunnelProviderManager + typealias TunnelProviderManagerType = SimulatorTunnelProviderManager #else -typealias TunnelProviderManagerType = NETunnelProviderManager + typealias TunnelProviderManagerType = NETunnelProviderManager #endif protocol TunnelStatusObserver { @@ -23,7 +23,7 @@ protocol TunnelStatusObserver { /// Tunnel wrapper class. class Tunnel { /// Tunnel provider manager. - fileprivate let tunnelProvider: TunnelProviderManagerType + private let tunnelProvider: TunnelProviderManagerType /// Tunnel start date. /// @@ -90,7 +90,10 @@ class Tunnel { tunnelProvider.removeFromPreferences(completionHandler: completion) } - func addBlockObserver(queue: DispatchQueue? = nil, handler: @escaping (Tunnel, NEVPNStatus) -> Void) -> TunnelStatusBlockObserver { + func addBlockObserver( + queue: DispatchQueue? = nil, + handler: @escaping (Tunnel, NEVPNStatus) -> Void + ) -> TunnelStatusBlockObserver { let observer = TunnelStatusBlockObserver(tunnel: self, queue: queue, handler: handler) addObserver(observer) diff --git a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift index b3d2b7b495..b2f19523b0 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift @@ -9,7 +9,6 @@ import Foundation protocol TunnelInteractor { - // MARK: - Tunnel manipulation var tunnel: Tunnel? { get } diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index f42a60b84f..2014a90cc0 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -7,10 +7,10 @@ // import Foundation +import Logging import NetworkExtension -import UIKit import StoreKit -import Logging +import UIKit import class WireGuardKitTypes.PublicKey enum TunnelManagerConfiguration { @@ -50,12 +50,10 @@ final class TunnelManager { } } - static let shared: TunnelManager = { - return TunnelManager( - accountsProxy: REST.ProxyFactory.shared.createAccountsProxy(), - devicesProxy: REST.ProxyFactory.shared.createDevicesProxy() - ) - }() + static let shared = TunnelManager( + accountsProxy: REST.ProxyFactory.shared.createAccountsProxy(), + devicesProxy: REST.ProxyFactory.shared.createDevicesProxy() + ) // MARK: - Internal variables @@ -130,7 +128,7 @@ final class TunnelManager { nslock.lock() defer { nslock.unlock() } - guard case .loggedIn(_, let deviceData) = deviceState else { + guard case let .loggedIn(_, deviceData) = deviceState else { return nil } @@ -142,7 +140,8 @@ final class TunnelManager { // Do not rotate the key if account or device is not found. if let restError = completion.error as? REST.Error, restError.compareErrorCode(.invalidAccount) || - restError.compareErrorCode(.deviceNotFound) { + restError.compareErrorCode(.deviceNotFound) + { return nil } @@ -203,7 +202,6 @@ final class TunnelManager { lastKeyRotationData = nil updatePrivateKeyRotationTimer() - } // MARK: - Public methods @@ -223,7 +221,7 @@ final class TunnelManager { loadTunnelOperation.completionHandler = { [weak self] completion in guard let self = self else { return } - if case .failure(let error) = completion { + if case let .failure(error) = completion { self.logger.error( chainedError: AnyChainedError(error), message: "Failed to load configuration." @@ -237,7 +235,7 @@ final class TunnelManager { loadTunnelOperation.addDependency(migrateSettingsOperation) let groupOperation = GroupOperation(operations: [ - migrateSettingsOperation, loadTunnelOperation + migrateSettingsOperation, loadTunnelOperation, ]) groupOperation.addObserver( @@ -262,7 +260,7 @@ final class TunnelManager { _refreshTunnelStatus() } - func startTunnel(completionHandler: ((OperationCompletion<(), Error>) -> Void)? = nil) { + func startTunnel(completionHandler: ((OperationCompletion<Void, Error>) -> Void)? = nil) { let operation = StartTunnelOperation( dispatchQueue: internalQueue, interactor: TunnelInteractorProxy(self), @@ -285,7 +283,8 @@ final class TunnelManager { completionHandler?(completion) } - }) + } + ) operation.addObserver(BackgroundObserver(name: "Start tunnel", cancelUponExpiration: true)) operation.addCondition(MutuallyExclusive(category: OperationCategory.manageTunnel.category)) @@ -293,7 +292,7 @@ final class TunnelManager { operationQueue.addOperation(operation) } - func stopTunnel(completionHandler: ((OperationCompletion<(), Error>) -> Void)? = nil) { + func stopTunnel(completionHandler: ((OperationCompletion<Void, Error>) -> Void)? = nil) { let operation = StopTunnelOperation( dispatchQueue: internalQueue, interactor: TunnelInteractorProxy(self) @@ -326,9 +325,8 @@ final class TunnelManager { func reconnectTunnel( selectNewRelay: Bool, - completionHandler: ((OperationCompletion<(), Error>) -> Void)? = nil - ) - { + completionHandler: ((OperationCompletion<Void, Error>) -> Void)? = nil + ) { let operation = ReconnectTunnelOperation( dispatchQueue: internalQueue, interactor: TunnelInteractorProxy(self), @@ -352,7 +350,10 @@ final class TunnelManager { operationQueue.addOperation(operation) } - func setAccount(action: SetAccountAction, completionHandler: @escaping (OperationCompletion<StoredAccountData?, Error>) -> Void) { + func setAccount( + action: SetAccountAction, + completionHandler: @escaping (OperationCompletion<StoredAccountData?, Error>) -> Void + ) { let operation = SetAccountOperation( dispatchQueue: internalQueue, interactor: TunnelInteractorProxy(self), @@ -412,7 +413,10 @@ final class TunnelManager { operationQueue.addOperation(operation) } - func updateDeviceData(_ completionHandler: @escaping (OperationCompletion<StoredDeviceData, Error>) -> Void) -> Cancellable { + func updateDeviceData(_ completionHandler: @escaping (OperationCompletion< + StoredDeviceData, + Error + >) -> Void) -> Cancellable { let operation = UpdateDeviceDataOperation( dispatchQueue: internalQueue, interactor: TunnelInteractorProxy(self), @@ -471,7 +475,7 @@ final class TunnelManager { completionHandler(completion) } - case .failure(let error): + case let .failure(error): self.checkIfDeviceRevoked(error) completionHandler(.failure(error)) @@ -494,7 +498,10 @@ final class TunnelManager { return operation } - func setRelayConstraints(_ newConstraints: RelayConstraints, completionHandler: (() -> Void)? = nil) { + func setRelayConstraints( + _ newConstraints: RelayConstraints, + completionHandler: (() -> Void)? = nil + ) { scheduleSettingsUpdate( taskName: "Set relay constraints", modificationBlock: { settings in @@ -613,8 +620,7 @@ final class TunnelManager { fileprivate func updateTunnelStatus( from packetTunnelStatus: PacketTunnelStatus, mappingRelayToState mapper: (PacketTunnelRelay?) -> TunnelState? - ) - { + ) { nslock.lock() defer { nslock.unlock() } @@ -654,7 +660,7 @@ final class TunnelManager { } DispatchQueue.main.async { - self.observerList.forEach { (observer) in + self.observerList.forEach { observer in observer.tunnelManager(self, didUpdateTunnelState: newTunnelStatus.state) } } @@ -746,7 +752,7 @@ final class TunnelManager { ) } - private func didReconnectTunnel(completion: OperationCompletion<(), Error>) { + private func didReconnectTunnel(completion: OperationCompletion<Void, Error>) { nslock.lock() defer { nslock.unlock() } @@ -776,12 +782,13 @@ final class TunnelManager { unsubscribeVPNStatusObserver() - statusObserver = tunnel.addBlockObserver(queue: internalQueue) { [weak self] tunnel, status in - guard let self = self else { return } + statusObserver = tunnel + .addBlockObserver(queue: internalQueue) { [weak self] tunnel, status in + guard let self = self else { return } - self.logger.debug("VPN connection status changed to \(status).") - self.updateTunnelStatus(status) - } + self.logger.debug("VPN connection status changed to \(status).") + self.updateTunnelStatus(status) + } } private func unsubscribeVPNStatusObserver() { @@ -829,8 +836,7 @@ final class TunnelManager { taskName: String, modificationBlock: @escaping (inout TunnelSettingsV2) -> Void, completionHandler: (() -> Void)? - ) - { + ) { let operation = AsyncBlockOperation(dispatchQueue: internalQueue) { let currentSettings = self._tunnelSettings var updatedSettings = self._tunnelSettings @@ -864,8 +870,7 @@ final class TunnelManager { taskName: String, modificationBlock: @escaping (inout DeviceState) -> Void, completionHandler: (() -> Void)? - ) - { + ) { let operation = AsyncBlockOperation(dispatchQueue: internalQueue) { var deviceState = self.deviceState @@ -891,7 +896,9 @@ final class TunnelManager { // MARK: - Tunnel status polling - private func computeNextPollDateAndRepeatInterval(connectingDate: Date?) -> (Date, TimeInterval) { + private func computeNextPollDateAndRepeatInterval(connectingDate: Date?) + -> (Date, TimeInterval) + { let delay, repeating: TimeInterval let fireDate: Date @@ -929,8 +936,14 @@ final class TunnelManager { lastConnectingDate = connectingDate isPolling = true - let (fireDate, repeating) = computeNextPollDateAndRepeatInterval(connectingDate: connectingDate) - logger.debug("Start polling tunnel status at \(fireDate.logFormatDate()) every \(repeating) second(s).") + let ( + fireDate, + repeating + ) = computeNextPollDateAndRepeatInterval(connectingDate: connectingDate) + logger + .debug( + "Start polling tunnel status at \(fireDate.logFormatDate()) every \(repeating) second(s)." + ) let timer = DispatchSource.makeTimerSource(queue: .main) timer.setEventHandler { [weak self] in @@ -961,7 +974,6 @@ final class TunnelManager { lastConnectingDate = nil isPolling = false } - } // MARK: - AppStore payment observer @@ -973,8 +985,7 @@ extension TunnelManager: AppStorePaymentObserver { payment: SKPayment, accountToken: String?, didFailWithError error: AppStorePaymentManager.Error - ) - { + ) { // no-op } @@ -983,8 +994,7 @@ extension TunnelManager: AppStorePaymentObserver { transaction: SKPaymentTransaction, accountToken: String, didFinishWithResponse response: REST.CreateApplePaymentResponse - ) - { + ) { scheduleDeviceStateUpdate( taskName: "Update account expiry after in-app purchase", modificationBlock: { deviceState in @@ -1030,8 +1040,7 @@ private struct TunnelInteractorProxy: TunnelInteractor { func updateTunnelStatus( from packetTunnelStatus: PacketTunnelStatus, mappingRelayToState mapper: (PacketTunnelRelay?) -> TunnelState? - ) - { + ) { tunnelManager.updateTunnelStatus(from: packetTunnelStatus, mappingRelayToState: mapper) } @@ -1074,5 +1083,4 @@ private struct TunnelInteractorProxy: TunnelInteractor { func prepareForVPNConfigurationDeletion() { tunnelManager.prepareForVPNConfigurationDeletion() } - } diff --git a/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift b/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift index c777052c39..2e82f59018 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift @@ -10,7 +10,6 @@ import Foundation import NetworkExtension protocol TunnelManagerStateDelegate: AnyObject { - func tunnelManagerState( _ state: TunnelManager.State, didChangeLoadedConfiguration isLoadedConfiguration: Bool @@ -34,7 +33,6 @@ protocol TunnelManagerStateDelegate: AnyObject { } extension TunnelManager { - class State { weak var delegate: TunnelManagerStateDelegate? let delegateQueue: DispatchQueue diff --git a/ios/MullvadVPN/TunnelManager/TunnelState.swift b/ios/MullvadVPN/TunnelManager/TunnelState.swift index bfbc0da3e9..fc06f1cd0d 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelState.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelState.swift @@ -33,7 +33,10 @@ struct TunnelStatus: Equatable, CustomStringConvertible { } /// Updates the tunnel status from packet tunnel status, mapping relay to tunnel state. - mutating func update(from packetTunnelStatus: PacketTunnelStatus, mappingRelayToState mapper: (PacketTunnelRelay?) -> TunnelState?) { + mutating func update( + from packetTunnelStatus: PacketTunnelStatus, + mappingRelayToState mapper: (PacketTunnelRelay?) -> TunnelState? + ) { self.packetTunnelStatus = packetTunnelStatus if let newState = mapper(packetTunnelStatus.tunnelRelay) { @@ -75,19 +78,19 @@ enum TunnelState: Equatable, CustomStringConvertible { switch self { case .pendingReconnect: return "pending reconnect after disconnect" - case .connecting(let tunnelRelay): + case let .connecting(tunnelRelay): if let tunnelRelay = tunnelRelay { return "connecting to \(tunnelRelay.hostname)" } else { return "connecting, fetching relay" } - case .connected(let tunnelRelay): + case let .connected(tunnelRelay): return "connected to \(tunnelRelay.hostname)" - case .disconnecting(let actionAfterDisconnect): + case let .disconnecting(actionAfterDisconnect): return "disconnecting and then \(actionAfterDisconnect)" case .disconnected: return "disconnected" - case .reconnecting(let tunnelRelay): + case let .reconnecting(tunnelRelay): return "reconnecting to \(tunnelRelay.hostname)" } } diff --git a/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift b/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift index 25669631c5..d0f62c68e4 100644 --- a/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift +++ b/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift @@ -19,8 +19,7 @@ class UpdateAccountDataOperation: ResultOperation<Void, Error> { dispatchQueue: DispatchQueue, interactor: TunnelInteractor, accountsProxy: REST.AccountsProxy - ) - { + ) { self.interactor = interactor self.accountsProxy = accountsProxy @@ -28,7 +27,7 @@ class UpdateAccountDataOperation: ResultOperation<Void, Error> { } override func main() { - guard case .loggedIn(let accountData, _) = interactor.deviceState else { + guard case let .loggedIn(accountData, _) = interactor.deviceState else { finish(completion: .failure(InvalidDeviceStateError())) return } diff --git a/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift b/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift index af20dbc2a1..da7bc3d7bd 100644 --- a/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift +++ b/ios/MullvadVPN/TunnelManager/UpdateDeviceDataOperation.swift @@ -20,8 +20,7 @@ class UpdateDeviceDataOperation: ResultOperation<StoredDeviceData, Error> { dispatchQueue: DispatchQueue, interactor: TunnelInteractor, devicesProxy: REST.DevicesProxy - ) - { + ) { self.interactor = interactor self.devicesProxy = devicesProxy @@ -29,7 +28,7 @@ class UpdateDeviceDataOperation: ResultOperation<StoredDeviceData, Error> { } override func main() { - guard case .loggedIn(let accountData, let deviceData) = interactor.deviceState else { + guard case let .loggedIn(accountData, deviceData) = interactor.deviceState else { finish(completion: .failure(InvalidDeviceStateError())) return } @@ -44,7 +43,8 @@ class UpdateDeviceDataOperation: ResultOperation<StoredDeviceData, Error> { completion: completion ) } - }) + } + ) } override func operationDidCancel() { @@ -71,5 +71,4 @@ class UpdateDeviceDataOperation: ResultOperation<StoredDeviceData, Error> { finish(completion: mappedCompletion) } - } diff --git a/ios/MullvadVPN/UIBarButtonItem+KeyboardNavigation.swift b/ios/MullvadVPN/UIBarButtonItem+KeyboardNavigation.swift index ee81308440..f42e7a2f7b 100644 --- a/ios/MullvadVPN/UIBarButtonItem+KeyboardNavigation.swift +++ b/ios/MullvadVPN/UIBarButtonItem+KeyboardNavigation.swift @@ -9,7 +9,6 @@ import UIKit extension UIBarButtonItem { - enum KeyboardNavigationItemType { case previous, next @@ -43,18 +42,43 @@ extension UIBarButtonItem { } } - convenience init(keyboardNavigationItemType: KeyboardNavigationItemType, target: Any?, action: Selector?) { + convenience init( + keyboardNavigationItemType: KeyboardNavigationItemType, + target: Any?, + action: Selector? + ) { if #available(iOS 13, *) { - self.init(image: keyboardNavigationItemType.systemImage, style: .plain, target: target, action: action) + self.init( + image: keyboardNavigationItemType.systemImage, + style: .plain, + target: target, + action: action + ) } else { - self.init(title: keyboardNavigationItemType.localizedTitle, style: .plain, target: target, action: action) + self.init( + title: keyboardNavigationItemType.localizedTitle, + style: .plain, + target: target, + action: action + ) } accessibilityLabel = keyboardNavigationItemType.localizedTitle } - static func makeKeyboardNavigationItems(_ configurationBlock: (_ prevItem: UIBarButtonItem, _ nextItem: UIBarButtonItem) -> Void) -> [UIBarButtonItem] { - let prevButton = UIBarButtonItem(keyboardNavigationItemType: .previous, target: nil, action: nil) - let nextButton = UIBarButtonItem(keyboardNavigationItemType: .next, target: nil, action: nil) + static func makeKeyboardNavigationItems(_ configurationBlock: ( + _ prevItem: UIBarButtonItem, + _ nextItem: UIBarButtonItem + ) -> Void) -> [UIBarButtonItem] { + let prevButton = UIBarButtonItem( + keyboardNavigationItemType: .previous, + target: nil, + action: nil + ) + let nextButton = UIBarButtonItem( + keyboardNavigationItemType: .next, + target: nil, + action: nil + ) configurationBlock(prevButton, nextButton) @@ -67,5 +91,4 @@ extension UIBarButtonItem { return [prevButton, nextButton] } } - } diff --git a/ios/MullvadVPN/UIColor+Helpers.swift b/ios/MullvadVPN/UIColor+Helpers.swift index 08f36a32ac..a119f64660 100644 --- a/ios/MullvadVPN/UIColor+Helpers.swift +++ b/ios/MullvadVPN/UIColor+Helpers.swift @@ -9,7 +9,6 @@ import UIKit extension UIColor { - /// Returns the color lighter by the given percent (in range from 0..1) func lightened(by percent: CGFloat) -> UIColor? { return darkened(by: -percent) @@ -21,15 +20,16 @@ extension UIColor { let factor = 1.0 - percent if getRed(&r, green: &g, blue: &b, alpha: &a) { - return UIColor(red: clampColorComponent(r * factor), - green: clampColorComponent(g * factor), - blue: clampColorComponent(b * factor), - alpha: a) + return UIColor( + red: clampColorComponent(r * factor), + green: clampColorComponent(g * factor), + blue: clampColorComponent(b * factor), + alpha: a + ) } return nil } - } private func clampColorComponent(_ value: CGFloat) -> CGFloat { diff --git a/ios/MullvadVPN/UIColor+Palette.swift b/ios/MullvadVPN/UIColor+Palette.swift index dcb8c98e45..0617868656 100644 --- a/ios/MullvadVPN/UIColor+Palette.swift +++ b/ios/MullvadVPN/UIColor+Palette.swift @@ -9,7 +9,6 @@ import UIKit extension UIColor { - enum AccountTextField { enum NormalState { static let borderColor = secondaryColor diff --git a/ios/MullvadVPN/UIFont+Monospaced.swift b/ios/MullvadVPN/UIFont+Monospaced.swift index 5ce7bb633b..0690070e01 100644 --- a/ios/MullvadVPN/UIFont+Monospaced.swift +++ b/ios/MullvadVPN/UIFont+Monospaced.swift @@ -17,8 +17,8 @@ extension UIFont { let fontDescriptor = UIFontDescriptor(fontAttributes: [ .name: "Menlo", .traits: [ - UIFontDescriptor.TraitKey.weight: weight - ] + UIFontDescriptor.TraitKey.weight: weight, + ], ]) return UIFont(descriptor: fontDescriptor, size: size) diff --git a/ios/MullvadVPN/UIImage+TintColor.swift b/ios/MullvadVPN/UIImage+TintColor.swift index b8b7dd1731..46fd6326bf 100644 --- a/ios/MullvadVPN/UIImage+TintColor.swift +++ b/ios/MullvadVPN/UIImage+TintColor.swift @@ -9,7 +9,6 @@ import UIKit extension UIImage { - func backport_withTintColor(_ tintColor: UIColor) -> UIImage { return backport_withTintColor(tintColor, renderingMode: renderingMode) } @@ -30,5 +29,4 @@ extension UIImage { return renderedImage.withRenderingMode(renderingMode) } - } diff --git a/ios/MullvadVPN/UIMetrics.swift b/ios/MullvadVPN/UIMetrics.swift index db45331197..1e9ee912c1 100644 --- a/ios/MullvadVPN/UIMetrics.swift +++ b/ios/MullvadVPN/UIMetrics.swift @@ -11,7 +11,6 @@ import UIKit enum UIMetrics {} extension UIMetrics { - /// Common layout margins for content presentation static let contentLayoutMargins = UIEdgeInsets(top: 24, left: 24, bottom: 24, right: 24) @@ -23,13 +22,23 @@ extension UIMetrics { static let settingsCellLayoutMargins = UIEdgeInsets(top: 16, left: 24, bottom: 16, right: 12) /// Common layout margins for location cell presentation - static let selectLocationCellLayoutMargins = UIEdgeInsets(top: 16, left: 28, bottom: 16, right: 12) + static let selectLocationCellLayoutMargins = UIEdgeInsets( + top: 16, + left: 28, + bottom: 16, + right: 12 + ) /// Common cell indentation width static let cellIndentationWidth: CGFloat = 16 /// Layout margins for in-app notification banner presentation - static let inAppBannerNotificationLayoutMargins = UIEdgeInsets(top: 16, left: 24, bottom: 16, right: 24) + static let inAppBannerNotificationLayoutMargins = UIEdgeInsets( + top: 16, + left: 24, + bottom: 16, + right: 24 + ) /// Spacing used in stack views of buttons static let interButtonSpacing: CGFloat = 16 @@ -51,5 +60,4 @@ extension UIMetrics { /// Maximum sidebar width in percentage points (0...1) static let maximumSplitViewSidebarWidthFraction: CGFloat = 0.3 - } diff --git a/ios/MullvadVPN/UserInterfaceInteractionRestriction.swift b/ios/MullvadVPN/UserInterfaceInteractionRestriction.swift index 2a07132c09..ca5e63dfce 100644 --- a/ios/MullvadVPN/UserInterfaceInteractionRestriction.swift +++ b/ios/MullvadVPN/UserInterfaceInteractionRestriction.swift @@ -18,8 +18,7 @@ protocol UserInterfaceInteractionRestrictionProtocol { } /// A counter based user interface interaction restriction implementation -class UserInterfaceInteractionRestriction: UserInterfaceInteractionRestrictionProtocol -{ +class UserInterfaceInteractionRestriction: UserInterfaceInteractionRestrictionProtocol { typealias Action = (_ enableUserInteraction: Bool, _ animated: Bool) -> Void private let action: Action diff --git a/ios/MullvadVPN/WireguardKeysContentView.swift b/ios/MullvadVPN/WireguardKeysContentView.swift index 4086047974..cc978bd218 100644 --- a/ios/MullvadVPN/WireguardKeysContentView.swift +++ b/ios/MullvadVPN/WireguardKeysContentView.swift @@ -9,7 +9,6 @@ import UIKit class WireguardKeysContentView: UIView { - let regenerateKeyButton: AppButton = { let button = AppButton(style: .success) button.translatesAutoresizingMaskIntoConstraints = false @@ -81,10 +80,13 @@ class WireguardKeysContentView: UIView { contentStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), contentStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - buttonStackView.topAnchor.constraint(greaterThanOrEqualTo: contentStackView.bottomAnchor, constant: UIMetrics.sectionSpacing), + buttonStackView.topAnchor.constraint( + greaterThanOrEqualTo: contentStackView.bottomAnchor, + constant: UIMetrics.sectionSpacing + ), buttonStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), buttonStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - buttonStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + buttonStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), ]) } @@ -94,7 +96,6 @@ class WireguardKeysContentView: UIView { } class WireguardKeysPublicKeyRow: UIView { - var value: String? { didSet { valueButton.setTitle(value, for: .normal) @@ -159,7 +160,10 @@ class WireguardKeysPublicKeyRow: UIView { NSLayoutConstraint.activate([ textLabel.topAnchor.constraint(equalTo: topAnchor), textLabel.leadingAnchor.constraint(equalTo: leadingAnchor), - textLabel.trailingAnchor.constraint(greaterThanOrEqualTo: statusView.leadingAnchor, constant: -8), + textLabel.trailingAnchor.constraint( + greaterThanOrEqualTo: statusView.leadingAnchor, + constant: -8 + ), statusView.topAnchor.constraint(equalTo: textLabel.topAnchor), statusView.bottomAnchor.constraint(equalTo: textLabel.bottomAnchor), @@ -168,7 +172,7 @@ class WireguardKeysPublicKeyRow: UIView { valueButton.topAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 8), valueButton.leadingAnchor.constraint(equalTo: leadingAnchor), valueButton.trailingAnchor.constraint(equalTo: trailingAnchor), - valueButton.bottomAnchor.constraint(equalTo: bottomAnchor) + valueButton.bottomAnchor.constraint(equalTo: bottomAnchor), ]) isAccessibilityElement = true @@ -185,7 +189,7 @@ class WireguardKeysPublicKeyRow: UIView { name: actionName, target: self, selector: #selector(performAccessibilityAction) - ) + ), ] valueButton.addTarget(self, action: #selector(handleTap), for: .touchUpInside) @@ -196,9 +200,9 @@ class WireguardKeysPublicKeyRow: UIView { } private func updateAccessibilityLabel() { - var accessibilityLabelString = self.textLabel.text ?? "" + var accessibilityLabelString = textLabel.text ?? "" - if case .verified(let isValid) = status { + if case let .verified(isValid) = status { accessibilityLabelString += ", " if isValid { @@ -230,12 +234,11 @@ class WireguardKeysPublicKeyRow: UIView { } @objc private func performAccessibilityAction() { - self.actionHandler?() + actionHandler?() } } class WireguardKeysCreationRow: UIView { - var value: String? { didSet { accessibilityValue = value @@ -279,7 +282,7 @@ class WireguardKeysCreationRow: UIView { valueLabel.topAnchor.constraint(equalTo: textLabel.bottomAnchor, constant: 8), valueLabel.leadingAnchor.constraint(equalTo: leadingAnchor), valueLabel.trailingAnchor.constraint(equalTo: trailingAnchor), - valueLabel.bottomAnchor.constraint(equalTo: bottomAnchor) + valueLabel.bottomAnchor.constraint(equalTo: bottomAnchor), ]) isAccessibilityElement = true @@ -335,7 +338,7 @@ class WireguardKeyStatusView: UIView { stackView.topAnchor.constraint(equalTo: topAnchor), stackView.leadingAnchor.constraint(equalTo: leadingAnchor), stackView.bottomAnchor.constraint(equalTo: bottomAnchor), - stackView.trailingAnchor.constraint(equalTo: trailingAnchor) + stackView.trailingAnchor.constraint(equalTo: trailingAnchor), ]) updateView() @@ -354,7 +357,7 @@ class WireguardKeyStatusView: UIView { case .regenerating, .verifying: startSpinner() - case .verified(let isValid): + case let .verified(isValid): textLabel.isHidden = false activityIndicator.stopAnimating() @@ -382,5 +385,4 @@ class WireguardKeyStatusView: UIView { textLabel.isHidden = true activityIndicator.startAnimating() } - } diff --git a/ios/MullvadVPN/WireguardKeysViewController.swift b/ios/MullvadVPN/WireguardKeysViewController.swift index a177519483..0a5f751058 100644 --- a/ios/MullvadVPN/WireguardKeysViewController.swift +++ b/ios/MullvadVPN/WireguardKeysViewController.swift @@ -7,8 +7,8 @@ // import Foundation -import UIKit import Logging +import UIKit /// A UI refresh interval for the public key creation date (in seconds) private let creationDateRefreshInterval = Int(60) @@ -25,7 +25,6 @@ private enum WireguardKeysViewState { } class WireguardKeysViewController: UIViewController, TunnelObserver { - private let contentView: WireguardKeysContentView = { let contentView = WireguardKeysContentView() contentView.translatesAutoresizingMaskIntoConstraints = false @@ -64,7 +63,8 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), contentView.topAnchor.constraint(equalTo: scrollView.topAnchor), - contentView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.bottomAnchor), + contentView.bottomAnchor + .constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.bottomAnchor), contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), @@ -81,8 +81,16 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { self?.copyPublicKey() } - contentView.regenerateKeyButton.addTarget(self, action: #selector(handleRegenerateKey(_:)), for: .touchUpInside) - contentView.verifyKeyButton.addTarget(self, action: #selector(handleVerifyKey(_:)), for: .touchUpInside) + contentView.regenerateKeyButton.addTarget( + self, + action: #selector(handleRegenerateKey(_:)), + for: .touchUpInside + ) + contentView.verifyKeyButton.addTarget( + self, + action: #selector(handleVerifyKey(_:)), + for: .touchUpInside + ) TunnelManager.shared.addObserver(self) updatePublicKey(deviceData: TunnelManager.shared.deviceState.deviceData, animated: false) @@ -93,13 +101,16 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { private func startPublicKeyPeriodicUpdate() { let interval = DispatchTimeInterval.seconds(creationDateRefreshInterval) let timerSource = DispatchSource.makeTimerSource(queue: .main) - timerSource.setEventHandler { [weak self] () -> Void in - self?.updatePublicKey(deviceData: TunnelManager.shared.deviceState.deviceData, animated: true) + timerSource.setEventHandler { [weak self] () in + self?.updatePublicKey( + deviceData: TunnelManager.shared.deviceState.deviceData, + animated: true + ) } timerSource.schedule(deadline: .now() + interval, repeating: interval) timerSource.activate() - self.publicKeyPeriodicUpdateTimer = timerSource + publicKeyPeriodicUpdateTimer = timerSource } // MARK: - TunnelObserver @@ -112,8 +123,11 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { // no-op } - func tunnelManager(_ manager: TunnelManager, didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2) { - // no-op + func tunnelManager( + _ manager: TunnelManager, + didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2 + ) { + // no-op } func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState) { @@ -138,7 +152,8 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { value: "COPIED TO PASTEBOARD!", comment: "" ), - animated: true) + animated: true + ) let workItem = DispatchWorkItem { [weak self] in self?.updatePublicKey( @@ -167,7 +182,7 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { from: creationDate, to: Date(), unitsStyle: .full - ).map { (formattedInterval) -> String in + ).map { formattedInterval -> String in return String( format: NSLocalizedString( "KEY_GENERATED_SINCE_FORMAT", @@ -181,7 +196,8 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { } private func updateCreationDateLabel(with creationDate: Date) { - contentView.creationRowView.value = formatKeyGenerationElapsedTime(with: creationDate) ?? "-" + contentView.creationRowView + .value = formatKeyGenerationElapsedTime(with: creationDate) ?? "-" } private func updatePublicKey(deviceData: StoredDeviceData?, animated: Bool) { @@ -210,7 +226,7 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { setKeyActionButtonsEnabled(false) contentView.publicKeyRowView.status = .verifying - case .verifiedKey(let isValid): + case let .verifiedKey(isValid): setKeyActionButtonsEnabled(true) contentView.publicKeyRowView.status = .verified(isValid) announceKeyVerificationResult(isValid: isValid) @@ -219,13 +235,12 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { setKeyActionButtonsEnabled(false) contentView.publicKeyRowView.status = .regenerating - case .regeneratedKey(let success): + case let .regeneratedKey(success): setKeyActionButtonsEnabled(true) contentView.publicKeyRowView.status = .default if success { announceKeyRegenerated() } - } } @@ -246,9 +261,10 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { case .success: self.updateViewState(.verifiedKey(true)) - case .failure(let error): + case let .failure(error): if let restError = error as? REST.Error, - restError.compareErrorCode(.deviceNotFound) { + restError.compareErrorCode(.deviceNotFound) + { self.updateViewState(.verifiedKey(false)) } else { self.showKeyVerificationFailureAlert(error) @@ -262,7 +278,7 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { } private func regeneratePrivateKey() { - self.updateViewState(.regeneratingKey) + updateViewState(.regeneratingKey) _ = TunnelManager.shared.rotatePrivateKey(forceRotate: true) { [weak self] completion in guard let self = self else { return } @@ -271,9 +287,10 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { case .success: self.updateViewState(.regeneratedKey(true)) - case .failure(let error): + case let .failure(error): if let restError = error as? REST.Error, - restError.compareErrorCode(.deviceNotFound) { + restError.compareErrorCode(.deviceNotFound) + { self.updateViewState(.regeneratedKey(false)) } else { self.showKeyRegenerationFailureAlert(error) @@ -349,7 +366,6 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { alertPresenter.enqueue(alertController, presentingController: self) } - private func setPublicKeyTitle(string: String, animated: Bool) { let updateTitle = { self.contentView.publicKeyRowView.value = string @@ -396,5 +412,4 @@ class WireguardKeysViewController: UIViewController, TunnelObserver { ) UIAccessibility.post(notification: .announcement, argument: announcementString) } - } diff --git a/ios/MullvadVPNScreenshots/MullvadVPNScreenshots.swift b/ios/MullvadVPNScreenshots/MullvadVPNScreenshots.swift index ab39539c3f..7cf720c959 100644 --- a/ios/MullvadVPNScreenshots/MullvadVPNScreenshots.swift +++ b/ios/MullvadVPNScreenshots/MullvadVPNScreenshots.swift @@ -9,7 +9,6 @@ import XCTest class MullvadVPNScreenshots: XCTestCase { - override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. diff --git a/ios/MullvadVPNTests/AccountTokenInputTests.swift b/ios/MullvadVPNTests/AccountTokenInputTests.swift index 75dd39f4bf..c7a0d2ccd5 100644 --- a/ios/MullvadVPNTests/AccountTokenInputTests.swift +++ b/ios/MullvadVPNTests/AccountTokenInputTests.swift @@ -11,7 +11,6 @@ import XCTest private let kSampleToken = "12345678" class AccountTokenInputTests: XCTestCase { - func testInitialValue() { let input = AccountTokenInput(string: kSampleToken) @@ -33,7 +32,8 @@ class AccountTokenInputTests: XCTestCase { input.replaceCharacters( in: input.formattedString.range(withOffset: 4, length: 1), replacementString: "", - emptySelection: true) + emptySelection: true + ) XCTAssertEqual(input.formattedString, "1235 678") XCTAssertEqual(input.caretPosition, 3) @@ -45,7 +45,8 @@ class AccountTokenInputTests: XCTestCase { input.replaceCharacters( in: input.formattedString.range(withOffset: 4, length: 1), replacementString: "", - emptySelection: false) + emptySelection: false + ) XCTAssertEqual(input.formattedString, "1234 5678") XCTAssertEqual(input.caretPosition, 4) @@ -57,7 +58,8 @@ class AccountTokenInputTests: XCTestCase { input.replaceCharacters( in: input.formattedString.range(withOffset: 7, length: 2), replacementString: "", - emptySelection: false) + emptySelection: false + ) XCTAssertEqual(input.formattedString, "1234 56") XCTAssertEqual(input.caretPosition, 7) @@ -69,7 +71,8 @@ class AccountTokenInputTests: XCTestCase { input.replaceCharacters( in: input.formattedString.range(withOffset: 5, length: 0), replacementString: "0000", - emptySelection: true) + emptySelection: true + ) XCTAssertEqual(input.formattedString, "1234 0000 5678") XCTAssertEqual(input.caretPosition, 9) @@ -81,12 +84,12 @@ class AccountTokenInputTests: XCTestCase { input.replaceCharacters( in: input.formattedString.range(withOffset: 5, length: 4), replacementString: "0000", - emptySelection: false) + emptySelection: false + ) XCTAssertEqual(input.formattedString, "1234 0000") XCTAssertEqual(input.caretPosition, 9) } - } private extension String { diff --git a/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift b/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift index f2fdf623d0..a4301c68d1 100644 --- a/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift +++ b/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift @@ -9,7 +9,6 @@ import XCTest class CustomDateComponentsFormattingTests: XCTestCase { - func testCloseToOneDayFormatting() throws { var dateComponents = DateComponents() dateComponents.hour = 23 @@ -20,7 +19,7 @@ class CustomDateComponentsFormattingTests: XCTestCase { let result = CustomDateComponentsFormatting.localizedString( from: startDate, to: endDate, - calendar: self.calendar, + calendar: calendar, unitsStyle: .full ) @@ -36,7 +35,7 @@ class CustomDateComponentsFormattingTests: XCTestCase { let result = CustomDateComponentsFormatting.localizedString( from: startDate, to: endDate, - calendar: self.calendar, + calendar: calendar, unitsStyle: .full ) @@ -55,5 +54,4 @@ class CustomDateComponentsFormattingTests: XCTestCase { calendar.locale = Locale(identifier: "en_US_POSIX") return calendar } - } diff --git a/ios/MullvadVPNTests/DataSourceSnapshotTests.swift b/ios/MullvadVPNTests/DataSourceSnapshotTests.swift index 1d626e1688..4587df4a75 100644 --- a/ios/MullvadVPNTests/DataSourceSnapshotTests.swift +++ b/ios/MullvadVPNTests/DataSourceSnapshotTests.swift @@ -9,7 +9,6 @@ import XCTest class DataSourceSnapshotTests: XCTestCase { - func testInsertingItem() throws { var a = DataSourceSnapshot<String, Int>() var b = DataSourceSnapshot<String, Int>() @@ -65,7 +64,7 @@ class DataSourceSnapshotTests: XCTestCase { a.appendSections(["First", "Second"]) b.appendSections(["First", "Second"]) - a.appendItems([1, 2, 3 ,4], in: "First") + a.appendItems([1, 2, 3, 4], in: "First") a.appendItems([5, 6, 7, 8], in: "Second") b.appendItems([5, 1, 2, 8, 4], in: "First") @@ -76,13 +75,13 @@ class DataSourceSnapshotTests: XCTestCase { XCTAssertEqual(diff.indexPathsToDelete, [ IndexPath(row: 3, section: 1), IndexPath(row: 0, section: 1), - IndexPath(row: 2, section: 0) + IndexPath(row: 2, section: 0), ]) XCTAssertEqual(diff.indexPathsToInsert, [ IndexPath(row: 0, section: 0), IndexPath(row: 3, section: 0), - IndexPath(row: 1, section: 1) + IndexPath(row: 1, section: 1), ]) } @@ -100,12 +99,12 @@ class DataSourceSnapshotTests: XCTestCase { XCTAssertEqual(diff.indexPathsToDelete, [ IndexPath(row: 2, section: 0), - IndexPath(row: 0, section: 0) + IndexPath(row: 0, section: 0), ]) XCTAssertEqual(diff.indexPathsToInsert, [ IndexPath(row: 0, section: 0), - IndexPath(row: 2, section: 0) + IndexPath(row: 2, section: 0), ]) } @@ -143,5 +142,4 @@ class DataSourceSnapshotTests: XCTestCase { XCTAssertEqual(diff.indexPathsToReload, [IndexPath(row: 0, section: 0)]) XCTAssertEqual(diff.indexPathsToReconfigure, [IndexPath(row: 1, section: 0)]) } - } diff --git a/ios/MullvadVPNTests/OperationConditionTests.swift b/ios/MullvadVPNTests/OperationConditionTests.swift index 297b8128ee..80bf13b2da 100644 --- a/ios/MullvadVPNTests/OperationConditionTests.swift +++ b/ios/MullvadVPNTests/OperationConditionTests.swift @@ -77,7 +77,7 @@ class OperationConditionTests: XCTestCase { let expectToNeverExecute = expectation(description: "Expect child to never execute.") expectToNeverExecute.isInverted = true - let parent = ResultBlockOperation<(), URLError> { + let parent = ResultBlockOperation<Void, URLError> { throw URLError(.badURL) } @@ -140,5 +140,4 @@ class OperationConditionTests: XCTestCase { let expectations = [expectFirstOperationExecution, expectSecondOperationExecution] wait(for: expectations, timeout: 2, enforceOrder: true) } - } diff --git a/ios/MullvadVPNTests/OperationInputInjectionTests.swift b/ios/MullvadVPNTests/OperationInputInjectionTests.swift index d62214af42..52394f89f1 100644 --- a/ios/MullvadVPNTests/OperationInputInjectionTests.swift +++ b/ios/MullvadVPNTests/OperationInputInjectionTests.swift @@ -9,7 +9,6 @@ import XCTest class OperationInputInjectionTests: XCTestCase { - func testInject() throws { let provider = ResultBlockOperation<Int, Error> { return 1 @@ -86,5 +85,4 @@ class OperationInputInjectionTests: XCTestCase { XCTAssertEqual(consumer.output, "3") } - } diff --git a/ios/MullvadVPNTests/OperationObserverTests.swift b/ios/MullvadVPNTests/OperationObserverTests.swift index bc1c92b442..5c9d257fdc 100644 --- a/ios/MullvadVPNTests/OperationObserverTests.swift +++ b/ios/MullvadVPNTests/OperationObserverTests.swift @@ -9,7 +9,6 @@ import XCTest class OperationObserverTests: XCTestCase { - func testBlockObserver() throws { let expectDidAttach = expectation(description: "didAttach handler") let expectDidStart = expectation(description: "didStart handler") @@ -64,5 +63,4 @@ class OperationObserverTests: XCTestCase { let expectations = [expectDidAttach, expectDidCancel, expectDidStart, expectDidFinish] wait(for: expectations, timeout: 1, enforceOrder: true) } - } diff --git a/ios/MullvadVPNTests/OperationSmokeTests.swift b/ios/MullvadVPNTests/OperationSmokeTests.swift index 0aaf74ef8c..c739e4e3c9 100644 --- a/ios/MullvadVPNTests/OperationSmokeTests.swift +++ b/ios/MullvadVPNTests/OperationSmokeTests.swift @@ -9,12 +9,11 @@ import XCTest class OperationSmokeTests: XCTestCase { - func testBatch() { let expect = expectation(description: "Expect all operations to finish.") let operationQueue = AsyncOperationQueue() - let operations = (1...500).flatMap { i -> [Operation] in + let operations = (1 ... 500).flatMap { i -> [Operation] in let parent = BlockOperation() parent.cancel() @@ -35,5 +34,4 @@ class OperationSmokeTests: XCTestCase { waitForExpectations(timeout: 1) } - } diff --git a/ios/MullvadVPNTests/RelaySelectorTests.swift b/ios/MullvadVPNTests/RelaySelectorTests.swift index 8f997289db..82a602c951 100644 --- a/ios/MullvadVPNTests/RelaySelectorTests.swift +++ b/ios/MullvadVPNTests/RelaySelectorTests.swift @@ -6,11 +6,10 @@ // Copyright © 2019 Mullvad VPN AB. All rights reserved. // -import XCTest import Network +import XCTest class RelaySelectorTests: XCTestCase { - func testCountryConstraint() throws { let constraints = RelayConstraints(location: .only(.country("es"))) @@ -33,7 +32,6 @@ class RelaySelectorTests: XCTestCase { XCTAssertEqual(result.relay.hostname, "se6-wireguard") } - } private let sampleRelays = REST.ServerRelaysResponse( @@ -55,7 +53,7 @@ private let sampleRelays = REST.ServerRelaysResponse( city: "Stockholm", latitude: 59.3289, longitude: 18.0649 - ) + ), ], wireguard: REST.ServerWireguardTunnels( ipv4Gateway: .loopback, @@ -109,6 +107,7 @@ private let sampleRelays = REST.ServerRelaysResponse( ipv6AddrIn: .loopback, publicKey: Data(), includeInCountry: true - ) - ]) + ), + ] + ) ) diff --git a/ios/MullvadVPNTests/StringTests.swift b/ios/MullvadVPNTests/StringTests.swift index e5db3c096a..4f5c3511f3 100644 --- a/ios/MullvadVPNTests/StringTests.swift +++ b/ios/MullvadVPNTests/StringTests.swift @@ -9,7 +9,6 @@ import XCTest class StringTests: XCTestCase { - func testEmptyString() { XCTAssertTrue("".split(every: 4).isEmpty) } @@ -25,5 +24,4 @@ class StringTests: XCTestCase { func testStringShorterThanLength() { XCTAssertEqual("1".split(every: 4), ["1"]) } - } diff --git a/ios/PacketTunnel/PacketTunnelConfiguration.swift b/ios/PacketTunnel/PacketTunnelConfiguration.swift index 69de080868..ef774489da 100644 --- a/ios/PacketTunnel/PacketTunnelConfiguration.swift +++ b/ios/PacketTunnel/PacketTunnelConfiguration.swift @@ -7,8 +7,8 @@ // import Foundation -import WireGuardKit import protocol Network.IPAddress +import WireGuardKit struct PacketTunnelConfiguration { var deviceState: DeviceState @@ -30,7 +30,7 @@ extension PacketTunnelConfiguration { peerConfig.endpoint = endpoint peerConfig.allowedIPs = [ IPAddressRange(from: "0.0.0.0/0")!, - IPAddressRange(from: "::/0")! + IPAddressRange(from: "::/0")!, ] return peerConfig } @@ -38,7 +38,7 @@ extension PacketTunnelConfiguration { var interfaceConfig: InterfaceConfiguration switch deviceState { - case .loggedIn(_, let device): + case let .loggedIn(_, device): interfaceConfig = InterfaceConfiguration(privateKey: device.wgKeyData.privateKey) interfaceConfig.addresses = [device.ipv4Address, device.ipv6Address] interfaceConfig.dns = dnsServers.map { DNSServer(address: $0) } diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift index 01a3da6e03..89de247198 100644 --- a/ios/PacketTunnel/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider.swift @@ -7,9 +7,9 @@ // import Foundation +import Logging import Network import NetworkExtension -import Logging import WireGuardKit class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate { @@ -71,17 +71,24 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate { super.init() - adapter = WireGuardAdapter(with: self, shouldHandleReasserting: false, logHandler: { [weak self] (logLevel, message) in - self?.dispatchQueue.async { - self?.tunnelLogger.log(level: logLevel.loggerLevel, "\(message)") + adapter = WireGuardAdapter( + with: self, + shouldHandleReasserting: false, + logHandler: { [weak self] logLevel, message in + self?.dispatchQueue.async { + self?.tunnelLogger.log(level: logLevel.loggerLevel, "\(message)") + } } - }) + ) tunnelMonitor = TunnelMonitor(queue: dispatchQueue, adapter: adapter) tunnelMonitor.delegate = self } - override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { + override func startTunnel( + options: [String: NSObject]?, + completionHandler: @escaping (Error?) -> Void + ) { let tunnelOptions = PacketTunnelOptions(rawOptions: options ?? [:]) var appSelectorResult: RelaySelectorResult? @@ -90,7 +97,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate { appSelectorResult = try tunnelOptions.getSelectorResult() switch appSelectorResult { - case .some(let selectorResult): + case let .some(selectorResult): providerLogger.debug( "Start the tunnel via app, connect to \(selectorResult.relay.hostname)." ) @@ -107,9 +114,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate { providerLogger.error( chainedError: AnyChainedError(error), message: """ - Failed to decode relay selector result passed from the app. \ - Will continue by picking new relay. - """ + Failed to decode relay selector result passed from the app. \ + Will continue by picking new relay. + """ ) } @@ -166,7 +173,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate { } } - override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { + override func stopTunnel( + with reason: NEProviderStopReason, + completionHandler: @escaping () -> Void + ) { providerLogger.debug("Stop the tunnel: \(reason)") dispatchQueue.async { @@ -210,7 +220,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate { self.providerLogger.debug("Received app message: \(message)") switch message { - case .reconnectTunnel(let appSelectorResult): + case let .reconnectTunnel(appSelectorResult): self.providerLogger.debug("Reconnecting the tunnel...") self.reconnectTunnel(selectorResult: appSelectorResult) { [weak self] error in @@ -318,8 +328,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate { func tunnelMonitor( _ tunnelMonitor: TunnelMonitor, networkReachabilityStatusDidChange isNetworkReachable: Bool - ) - { + ) { self.isNetworkReachable = isNetworkReachable // Adjust the start reconnect date if tunnel monitor re-started pinging in response to @@ -351,8 +360,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate { private func reconnectTunnel( selectorResult aSelectorResult: RelaySelectorResult?, completionHandler: @escaping (Error?) -> Void - ) - { + ) { dispatchPrecondition(condition: .onQueue(dispatchQueue)) // Read tunnel configuration. diff --git a/ios/PacketTunnel/TunnelMonitor/Pinger.swift b/ios/PacketTunnel/TunnelMonitor/Pinger.swift index feb0fd678a..ca4071fef2 100644 --- a/ios/PacketTunnel/TunnelMonitor/Pinger.swift +++ b/ios/PacketTunnel/TunnelMonitor/Pinger.swift @@ -7,8 +7,8 @@ // import Foundation -import struct Network.IPv4Address import Logging +import struct Network.IPv4Address final class Pinger { // Sender identifier passed along with ICMP packet. @@ -39,7 +39,15 @@ final class Pinger { stop() - guard let newSocket = CFSocketCreate(kCFAllocatorDefault, AF_INET, SOCK_DGRAM, IPPROTO_ICMP, 0, nil, nil) else { + guard let newSocket = CFSocketCreate( + kCFAllocatorDefault, + AF_INET, + SOCK_DGRAM, + IPPROTO_ICMP, + 0, + nil, + nil + ) else { throw Error.createSocket } @@ -98,7 +106,11 @@ final class Pinger { } let sequenceNumber = nextSequenceNumber() - let packetData = Self.createICMPPacket(identifier: identifier, sequenceNumber: sequenceNumber, payload: nil) + let packetData = Self.createICMPPacket( + identifier: identifier, + sequenceNumber: sequenceNumber, + payload: nil + ) let bytesSent = packetData.withUnsafeBytes { dataBuffer -> Int in return withUnsafeBytes(of: &sa) { bufferPointer in @@ -153,13 +165,20 @@ final class Pinger { ) if result == -1 { - logger.error("Failed to bind socket to \"\(interfaceName)\" (index: \(index), errno: \(errno)).") + logger + .error( + "Failed to bind socket to \"\(interfaceName)\" (index: \(index), errno: \(errno))." + ) throw Error.bindSocket(errno) } } - private class func createICMPPacket(identifier: UInt16, sequenceNumber: UInt16, payload: Data?) -> Data { + private class func createICMPPacket( + identifier: UInt16, + sequenceNumber: UInt16, + payload: Data? + ) -> Data { // Create data buffer. var data = Data() diff --git a/ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift b/ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift index fd589e4aa1..60ee52a1ed 100644 --- a/ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift +++ b/ios/PacketTunnel/TunnelMonitor/TunnelMonitor.swift @@ -7,9 +7,9 @@ // import Foundation +import Logging import NetworkExtension import WireGuardKit -import Logging final class TunnelMonitor { private let adapter: WireGuardAdapter @@ -97,7 +97,7 @@ final class TunnelMonitor { } private func stopNoQueue(forRestart: Bool) { - if isStarted && !forRestart { + if isStarted, !forRestart { logger.debug("Stop tunnel monitor.") } @@ -142,7 +142,10 @@ final class TunnelMonitor { timer?.setEventHandler { [weak self] in self?.onWgStatsTimer() } - timer?.schedule(wallDeadline: .now(), repeating: TunnelMonitorConfiguration.wgStatsQueryInterval) + timer?.schedule( + wallDeadline: .now(), + repeating: TunnelMonitorConfiguration.wgStatsQueryInterval + ) timer?.resume() logger.debug("Set WG stats timer.") @@ -174,11 +177,14 @@ final class TunnelMonitor { return } - let oldNetworkBytesReceived = self.networkBytesReceived + let oldNetworkBytesReceived = networkBytesReceived networkBytesReceived = newNetworkBytesReceived if newNetworkBytesReceived < oldNetworkBytesReceived { - logger.debug("Stats was reset? newNetworkBytesReceived = \(newNetworkBytesReceived), oldNetworkBytesReceived = \(oldNetworkBytesReceived)") + logger + .debug( + "Stats was reset? newNetworkBytesReceived = \(newNetworkBytesReceived), oldNetworkBytesReceived = \(oldNetworkBytesReceived)" + ) return } @@ -194,7 +200,10 @@ final class TunnelMonitor { return } - if let nextAttemptDate = lastAttemptDate?.addingTimeInterval(TunnelMonitorConfiguration.connectionTimeout), nextAttemptDate <= Date() { + if let nextAttemptDate = lastAttemptDate? + .addingTimeInterval(TunnelMonitorConfiguration.connectionTimeout), + nextAttemptDate <= Date() + { // Reset the last recovery attempt date. lastAttemptDate = nextAttemptDate @@ -230,7 +239,10 @@ final class TunnelMonitor { setWgStatsTimer() delegateQueue.async { - self.delegate?.tunnelMonitor(self, networkReachabilityStatusDidChange: isNetworkReachable) + self.delegate?.tunnelMonitor( + self, + networkReachabilityStatusDidChange: isNetworkReachable + ) } } catch { let error = error as! Pinger.Error @@ -255,7 +267,10 @@ final class TunnelMonitor { lastAttemptDate = nil delegateQueue.async { - self.delegate?.tunnelMonitor(self, networkReachabilityStatusDidChange: isNetworkReachable) + self.delegate?.tunnelMonitor( + self, + networkReachabilityStatusDidChange: isNetworkReachable + ) } default: @@ -301,7 +316,7 @@ final class TunnelMonitor { } if let endIndex = endIndex { - return UInt64(string[startIndex..<endIndex]) + return UInt64(string[startIndex ..< endIndex]) } else { return nil } diff --git a/ios/PacketTunnel/WireGuardAdapterError+Localization.swift b/ios/PacketTunnel/WireGuardAdapterError+Localization.swift index 7568c65b87..6a88af373d 100644 --- a/ios/PacketTunnel/WireGuardAdapterError+Localization.swift +++ b/ios/PacketTunnel/WireGuardAdapterError+Localization.swift @@ -18,7 +18,7 @@ extension WireGuardAdapterError: LocalizedError { case .invalidState: return "Failure to perform an operation in such state." - case .dnsResolution(let resolutionErrors): + case let .dnsResolution(resolutionErrors): let detailedErrorDescription = resolutionErrors .enumerated() .map { index, dnsResolutionError in @@ -33,7 +33,7 @@ extension WireGuardAdapterError: LocalizedError { case .setNetworkSettings: return "Failure to set network settings." - case .startWireGuardBackend(let code): + case let .startWireGuardBackend(code): return "Failure to start WireGuard backend (error code: \(code))." } } diff --git a/ios/PacketTunnel/WireGuardLogLevel+Logging.swift b/ios/PacketTunnel/WireGuardLogLevel+Logging.swift index 443941c74e..4a59b8dbfb 100644 --- a/ios/PacketTunnel/WireGuardLogLevel+Logging.swift +++ b/ios/PacketTunnel/WireGuardLogLevel+Logging.swift @@ -7,8 +7,8 @@ // import Foundation -import WireGuardKit import Logging +import WireGuardKit extension WireGuardLogLevel { var loggerLevel: Logger.Level { |
