summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@kvadrat.se>2023-12-20 10:56:20 +0100
committerBug Magnet <marco.nikic@mullvad.net>2023-12-20 11:25:47 +0100
commit23e879f6a4d893d9664e241c2350f09b74509def (patch)
tree301b42b9a5dffe86d8aaf1ed085c9c82f3b9f10f
parentd9dca33b0f34e039d37a5e1b4dbc7c0656c5d776 (diff)
downloadmullvadvpn-23e879f6a4d893d9664e241c2350f09b74509def.tar.xz
mullvadvpn-23e879f6a4d893d9664e241c2350f09b74509def.zip
Fix Swiftlint warnings
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj16
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift1
-rw-r--r--ios/MullvadVPN/Views/AppButton.swift230
-rw-r--r--ios/MullvadVPN/Views/CustomButton.swift190
-rw-r--r--ios/MullvadVPN/Views/LinkButton.swift58
-rw-r--r--ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift7
-rw-r--r--ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift28
-rw-r--r--ios/Shared/ApplicationConfiguration.swift6
9 files changed, 281 insertions, 259 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 63022d9507..811b045d47 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -142,7 +142,7 @@
58677712290976FB006F721F /* SettingsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58677711290976FB006F721F /* SettingsInteractor.swift */; };
5867771429097BCD006F721F /* PaymentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867771329097BCD006F721F /* PaymentState.swift */; };
5867771629097C5B006F721F /* ProductState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867771529097C5B006F721F /* ProductState.swift */; };
- 5868585524054096000B8131 /* AppButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5868585424054096000B8131 /* AppButton.swift */; };
+ 5868585524054096000B8131 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5868585424054096000B8131 /* CustomButton.swift */; };
58695AA02A4ADA9200328DB3 /* TunnelObfuscationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58695A9F2A4ADA9200328DB3 /* TunnelObfuscationTests.swift */; };
58695AA72A4B109F00328DB3 /* TunnelObfuscation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5840231F2A406BF5007B27AC /* TunnelObfuscation.framework */; };
586A0DCB2A20E359006C731C /* MullvadTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223D5294C8E5E0029F5F8 /* MullvadTypes.framework */; };
@@ -507,6 +507,8 @@
7A3FD1B72AD54ABD0042BEA6 /* AnyTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB982A98F4ED00F578F2 /* AnyTransport.swift */; };
7A3FD1B82AD54AE60042BEA6 /* TimeServerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB9A2A98F58600F578F2 /* TimeServerProxy.swift */; };
7A42DEC92A05164100B209BE /* SettingsInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */; };
+ 7A5869952B32E9C700640D27 /* LinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869942B32E9C700640D27 /* LinkButton.swift */; };
+ 7A5869972B32EA4500640D27 /* AppButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869962B32EA4500640D27 /* AppButton.swift */; };
7A6B4F592AB8412E00123853 /* TunnelMonitorTimings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */; };
7A6F2FA52AFA3CB2006D0856 /* AccountExpiryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */; };
7A6F2FA72AFBB9AE006D0856 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; };
@@ -1350,7 +1352,7 @@
58677711290976FB006F721F /* SettingsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInteractor.swift; sourceTree = "<group>"; };
5867771329097BCD006F721F /* PaymentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentState.swift; sourceTree = "<group>"; };
5867771529097C5B006F721F /* ProductState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductState.swift; sourceTree = "<group>"; };
- 5868585424054096000B8131 /* AppButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppButton.swift; sourceTree = "<group>"; };
+ 5868585424054096000B8131 /* CustomButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = "<group>"; };
58695A9D2A4ADA9100328DB3 /* TunnelObfuscationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TunnelObfuscationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
58695A9F2A4ADA9200328DB3 /* TunnelObfuscationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelObfuscationTests.swift; sourceTree = "<group>"; };
586A95112901321B007BAF2B /* IPv6Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv6Endpoint.swift; sourceTree = "<group>"; };
@@ -1646,6 +1648,8 @@
7A3353962AAA0F8600F0A71C /* OperationBlockObserverSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationBlockObserverSupport.swift; sourceTree = "<group>"; };
7A3FD1B42AD4465A0042BEA6 /* AppMessageHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMessageHandlerTests.swift; sourceTree = "<group>"; };
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInputCell.swift; sourceTree = "<group>"; };
+ 7A5869942B32E9C700640D27 /* LinkButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkButton.swift; sourceTree = "<group>"; };
+ 7A5869962B32EA4500640D27 /* AppButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppButton.swift; sourceTree = "<group>"; };
7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorTimings.swift; sourceTree = "<group>"; };
7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryTests.swift; sourceTree = "<group>"; };
7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiry.swift; sourceTree = "<group>"; };
@@ -2302,8 +2306,9 @@
583FE01F29C197ED006E85F9 /* Views */ = {
isa = PBXGroup;
children = (
- 5868585424054096000B8131 /* AppButton.swift */,
+ 7A5869962B32EA4500640D27 /* AppButton.swift */,
7A9FA1412A2E3306000B728D /* CheckboxView.swift */,
+ 5868585424054096000B8131 /* CustomButton.swift */,
58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */,
58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */,
58293FB025124117005D0BB5 /* CustomTextField.swift */,
@@ -2311,6 +2316,7 @@
5892A45D265FABFF00890742 /* EmptyTableViewHeaderFooterView.swift */,
58FD5BF32428C67600112C88 /* InAppPurchaseButton.swift */,
F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */,
+ 7A5869942B32E9C700640D27 /* LinkButton.swift */,
58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */,
E1FD0DF428AA7CE400299DB4 /* StatusActivityView.swift */,
58EF581025D69DB400AEBA94 /* StatusImageView.swift */,
@@ -4616,6 +4622,7 @@
5846227126E229F20035F7C2 /* StoreSubscription.swift in Sources */,
58421030282D8A3C00F24E46 /* UpdateAccountDataOperation.swift in Sources */,
F0E8E4C92A604E7400ED26A3 /* AccountDeletionInteractor.swift in Sources */,
+ 7A5869952B32E9C700640D27 /* LinkButton.swift in Sources */,
F09A297D2A9F8A9B00EA3B6F /* RedeemVoucherContentView.swift in Sources */,
5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */,
587EB672271451E300123C75 /* PreferencesViewModel.swift in Sources */,
@@ -4636,6 +4643,7 @@
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */,
5878A27B2909649A0096FC88 /* CustomOverlayRenderer.swift in Sources */,
A91614D62B10B26B00F416EB /* TunnelControlViewModel.swift in Sources */,
+ 7A5869972B32EA4500640D27 /* AppButton.swift in Sources */,
586C0D8F2B03D88100E7CDD7 /* ProxyProtocolConfigurationItemIdentifier.swift in Sources */,
588527B2276B3F0700BAA373 /* LoadTunnelConfigurationOperation.swift in Sources */,
58DFF7D22B0256A300F864E0 /* MarkdownStylingOptions.swift in Sources */,
@@ -4744,7 +4752,7 @@
5827B0AE2B0F4CBE00CCBBA1 /* ProxyConfigurationViewControllerDelegate.swift in Sources */,
5827B0962B0DB2C100CCBBA1 /* ProxyConfigurationItemIdentifier.swift in Sources */,
586C0D8B2B03D84400E7CDD7 /* AddAccessMethodItemIdentifier.swift in Sources */,
- 5868585524054096000B8131 /* AppButton.swift in Sources */,
+ 5868585524054096000B8131 /* CustomButton.swift in Sources */,
58E25F812837BBBB002CFB2C /* SceneDelegate.swift in Sources */,
7A1A26492A29D48A00B978AA /* RelayFilterCellFactory.swift in Sources */,
5867771629097C5B006F721F /* ProductState.swift in Sources */,
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift
index 29689a7dcd..6d8ba9b92d 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodViewController.swift
@@ -72,7 +72,7 @@ class ListAccessMethodViewController: UIViewController, UITableViewDelegate {
addChild(contentController)
contentController.didMove(toParent: self)
- interactor.publisher.sink { newElements in
+ interactor.publisher.sink { _ in
self.updateDataSource(animated: true)
}
.store(in: &cancellables)
@@ -104,7 +104,7 @@ class ListAccessMethodViewController: UIViewController, UITableViewDelegate {
private func configureDataSource() {
dataSource = UITableViewDiffableDataSource(
tableView: tableView,
- cellProvider: { [weak self] tableView, indexPath, itemIdentifier in
+ cellProvider: { [weak self] _, indexPath, itemIdentifier in
self?.dequeueCell(at: indexPath, itemIdentifier: itemIdentifier)
}
)
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
index 5a7e40b8e6..6955c3870d 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
@@ -25,6 +25,7 @@ struct SettingsCellFactory: CellFactoryProtocol {
return cell
}
+ // swiftlint:disable:next function_body_length
func configureCell(_ cell: UITableViewCell, item: SettingsDataSource.Item, indexPath: IndexPath) {
switch item {
case .preferences:
diff --git a/ios/MullvadVPN/Views/AppButton.swift b/ios/MullvadVPN/Views/AppButton.swift
index c63ac8aaca..245a0f5f9a 100644
--- a/ios/MullvadVPN/Views/AppButton.swift
+++ b/ios/MullvadVPN/Views/AppButton.swift
@@ -8,96 +8,6 @@
import UIKit
-enum ButtonImageAlignment {
- /// Align image at the left edge of the title label
- case left
-
- /// Align image at the right edge of the title label
- case right
-
- /// Align image at the leading edge of the title label
- case leading
-
- /// Align image at the trailing edge of the title label
- case trailing
-
- /// Align image at the leading edge of content area
- case leadingFixed
-
- /// Align image at the trailing edge of the content area
- case trailingFixed
-
- /// Align image at the left edge of the content area
- case leftFixed
-
- /// Align image at the right edge of the content area
- case rightFixed
-}
-
-private extension UIControl.State {
- var customButtonTitleColor: UIColor? {
- switch self {
- case .normal:
- return UIColor.AppButton.normalTitleColor
- case .disabled:
- return UIColor.AppButton.disabledTitleColor.withAlphaComponent(0.5)
- case .highlighted:
- return UIColor.AppButton.highlightedTitleColor
- default:
- return nil
- }
- }
-}
-
-/// 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()
- }
-
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- commonInit()
- }
-
- var titleString: String? {
- didSet {
- updateAttributedTitle(string: titleString)
- }
- }
-
- private func commonInit() {
- imageAlignment = .trailing
- contentHorizontalAlignment = .leading
-
- accessibilityTraits.insert(.link)
- }
-
- private func updateAttributedTitle(string: String?) {
- let states: [UIControl.State] = [.normal, .highlighted, .disabled]
- 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 {
- var attributes: [NSAttributedString.Key: Any] = [
- .underlineStyle: NSUnderlineStyle.single.rawValue,
- ]
-
- if let titleColor = state.customButtonTitleColor {
- attributes[.foregroundColor] = titleColor
- }
-
- return NSAttributedString(string: title, attributes: attributes)
- }
-}
-
/// A subclass that implements action buttons used across the app
class AppButton: CustomButton {
/// Default content insets based on current trait collection.
@@ -260,146 +170,6 @@ class AppButton: CustomButton {
}
}
-/// A custom `UIButton` subclass that implements additional layouts for the image
-class CustomButton: UIButton {
- var imageAlignment: ButtonImageAlignment = .leading {
- didSet {
- invalidateIntrinsicContentSize()
- }
- }
-
- var inlineImageSpacing: CGFloat = 4 {
- didSet {
- invalidateIntrinsicContentSize()
- }
- }
-
- override var intrinsicContentSize: CGSize {
- var intrinsicSize = super.intrinsicContentSize
-
- // Add spacing between the image and title label in intrinsic size calculation
- if let imageSize = currentImage?.size, imageSize.width > 0 {
- intrinsicSize.width += inlineImageSpacing
- }
-
- return intrinsicSize
- }
-
- var effectiveImageAlignment: ButtonImageAlignment {
- switch (imageAlignment, effectiveUserInterfaceLayoutDirection) {
- case (.left, _),
- (.leading, .leftToRight),
- (.trailing, .rightToLeft):
- return .left
-
- case (.right, _),
- (.trailing, .leftToRight),
- (.leading, .rightToLeft):
- return .right
-
- case (.leftFixed, _),
- (.leadingFixed, .leftToRight),
- (.trailingFixed, .rightToLeft):
- return .leftFixed
-
- case (.rightFixed, _),
- (.trailingFixed, .leftToRight),
- (.leadingFixed, .rightToLeft):
- return .rightFixed
-
- default:
- fatalError()
- }
- }
-
- override init(frame: CGRect) {
- super.init(frame: frame)
- commonInit()
- }
-
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- commonInit()
- }
-
- private func commonInit() {
- // Align the text color with the tint color which is applied to the image view
- if let imageTintColor = UIControl.State.normal.customButtonTitleColor {
- tintColor = imageTintColor
- }
- }
-
- private func computeLayout(forContentRect contentRect: CGRect) -> (CGRect, CGRect) {
- var imageRect = super.imageRect(forContentRect: contentRect)
- var titleRect = super.titleRect(forContentRect: contentRect)
-
- switch (effectiveContentHorizontalAlignment, effectiveImageAlignment) {
- case (.left, .left):
- imageRect.origin.x = contentRect.minX
- titleRect.origin.x = imageRect.width > 0
- ? imageRect.maxX + inlineImageSpacing
- : contentRect.minX
-
- case (.left, .right):
- titleRect.origin.x = contentRect.minX
- imageRect.origin.x = titleRect.maxX + inlineImageSpacing
-
- case (.left, .leftFixed):
- imageRect.origin.x = contentRect.minX
- titleRect.origin.x = imageRect.width > 0
- ? imageRect.maxX + inlineImageSpacing
- : contentRect.minX
-
- case (.left, .rightFixed):
- imageRect.origin.x = contentRect.maxX - imageRect.width
- titleRect.origin.x = contentRect.minX
-
- case (.center, .leftFixed):
- imageRect.origin.x = contentRect.minX
- titleRect.origin.x = contentRect.midX - titleRect.width * 0.5
-
- case (.center, .rightFixed):
- imageRect.origin.x = contentRect.maxX - imageRect.width
- titleRect.origin.x = contentRect.midX - titleRect.width * 0.5
-
- case (.center, .left):
- titleRect.origin.x = contentRect.midX - titleRect.width * 0.5
- imageRect.origin.x = titleRect.minX - inlineImageSpacing - imageRect.width
-
- case (.center, .right):
- titleRect.origin.x = contentRect.midX - titleRect.width * 0.5
- imageRect.origin.x = titleRect.maxX + inlineImageSpacing
-
- case (.right, .left):
- titleRect.origin.x = contentRect.maxX - titleRect.width
- imageRect.origin.x = titleRect.minX - imageRect.width - inlineImageSpacing
-
- case (.right, .leftFixed):
- imageRect.origin.x = contentRect.minX
- titleRect.origin.x = contentRect.maxX - titleRect.width
-
- case (.right, .rightFixed):
- imageRect.origin.x = contentRect.maxX - imageRect.width
- titleRect.origin.x = imageRect.width > 0
- ? imageRect.minX - inlineImageSpacing - titleRect.width
- : contentRect.maxX - titleRect.width
-
- default:
- fatalError()
- }
-
- return (titleRect, imageRect)
- }
-
- override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
- computeLayout(forContentRect: contentRect).1
- }
-
- override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
- computeLayout(forContentRect: contentRect).0
- }
-}
-
private extension AppButton {
class DynamicAssets {
static let shared = DynamicAssets()
diff --git a/ios/MullvadVPN/Views/CustomButton.swift b/ios/MullvadVPN/Views/CustomButton.swift
new file mode 100644
index 0000000000..46353bb1a6
--- /dev/null
+++ b/ios/MullvadVPN/Views/CustomButton.swift
@@ -0,0 +1,190 @@
+//
+// CustomButton.swift
+// MullvadVPN
+//
+// Created by pronebird on 23/05/2019.
+// Copyright © 2019 Mullvad VPN AB. All rights reserved.
+//
+
+import UIKit
+
+enum ButtonImageAlignment {
+ /// Align image at the left edge of the title label
+ case left
+
+ /// Align image at the right edge of the title label
+ case right
+
+ /// Align image at the leading edge of the title label
+ case leading
+
+ /// Align image at the trailing edge of the title label
+ case trailing
+
+ /// Align image at the leading edge of content area
+ case leadingFixed
+
+ /// Align image at the trailing edge of the content area
+ case trailingFixed
+
+ /// Align image at the left edge of the content area
+ case leftFixed
+
+ /// Align image at the right edge of the content area
+ case rightFixed
+}
+
+extension UIControl.State {
+ var customButtonTitleColor: UIColor? {
+ switch self {
+ case .normal:
+ return UIColor.AppButton.normalTitleColor
+ case .disabled:
+ return UIColor.AppButton.disabledTitleColor.withAlphaComponent(0.5)
+ case .highlighted:
+ return UIColor.AppButton.highlightedTitleColor
+ default:
+ return nil
+ }
+ }
+}
+
+/// A custom `UIButton` subclass that implements additional layouts for the image
+class CustomButton: UIButton {
+ var imageAlignment: ButtonImageAlignment = .leading {
+ didSet {
+ invalidateIntrinsicContentSize()
+ }
+ }
+
+ var inlineImageSpacing: CGFloat = 4 {
+ didSet {
+ invalidateIntrinsicContentSize()
+ }
+ }
+
+ override var intrinsicContentSize: CGSize {
+ var intrinsicSize = super.intrinsicContentSize
+
+ // Add spacing between the image and title label in intrinsic size calculation
+ if let imageSize = currentImage?.size, imageSize.width > 0 {
+ intrinsicSize.width += inlineImageSpacing
+ }
+
+ return intrinsicSize
+ }
+
+ var effectiveImageAlignment: ButtonImageAlignment {
+ switch (imageAlignment, effectiveUserInterfaceLayoutDirection) {
+ case (.left, _),
+ (.leading, .leftToRight),
+ (.trailing, .rightToLeft):
+ return .left
+
+ case (.right, _),
+ (.trailing, .leftToRight),
+ (.leading, .rightToLeft):
+ return .right
+
+ case (.leftFixed, _),
+ (.leadingFixed, .leftToRight),
+ (.trailingFixed, .rightToLeft):
+ return .leftFixed
+
+ case (.rightFixed, _),
+ (.trailingFixed, .leftToRight),
+ (.leadingFixed, .rightToLeft):
+ return .rightFixed
+
+ default:
+ fatalError()
+ }
+ }
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ commonInit()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ commonInit()
+ }
+
+ private func commonInit() {
+ // Align the text color with the tint color which is applied to the image view
+ if let imageTintColor = UIControl.State.normal.customButtonTitleColor {
+ tintColor = imageTintColor
+ }
+ }
+
+ private func computeLayout(forContentRect contentRect: CGRect) -> (CGRect, CGRect) {
+ var imageRect = super.imageRect(forContentRect: contentRect)
+ var titleRect = super.titleRect(forContentRect: contentRect)
+
+ switch (effectiveContentHorizontalAlignment, effectiveImageAlignment) {
+ case (.left, .left):
+ imageRect.origin.x = contentRect.minX
+ titleRect.origin.x = imageRect.width > 0
+ ? imageRect.maxX + inlineImageSpacing
+ : contentRect.minX
+
+ case (.left, .right):
+ titleRect.origin.x = contentRect.minX
+ imageRect.origin.x = titleRect.maxX + inlineImageSpacing
+
+ case (.left, .leftFixed):
+ imageRect.origin.x = contentRect.minX
+ titleRect.origin.x = imageRect.width > 0
+ ? imageRect.maxX + inlineImageSpacing
+ : contentRect.minX
+
+ case (.left, .rightFixed):
+ imageRect.origin.x = contentRect.maxX - imageRect.width
+ titleRect.origin.x = contentRect.minX
+
+ case (.center, .leftFixed):
+ imageRect.origin.x = contentRect.minX
+ titleRect.origin.x = contentRect.midX - titleRect.width * 0.5
+
+ case (.center, .rightFixed):
+ imageRect.origin.x = contentRect.maxX - imageRect.width
+ titleRect.origin.x = contentRect.midX - titleRect.width * 0.5
+
+ case (.center, .left):
+ titleRect.origin.x = contentRect.midX - titleRect.width * 0.5
+ imageRect.origin.x = titleRect.minX - inlineImageSpacing - imageRect.width
+
+ case (.center, .right):
+ titleRect.origin.x = contentRect.midX - titleRect.width * 0.5
+ imageRect.origin.x = titleRect.maxX + inlineImageSpacing
+
+ case (.right, .left):
+ titleRect.origin.x = contentRect.maxX - titleRect.width
+ imageRect.origin.x = titleRect.minX - imageRect.width - inlineImageSpacing
+
+ case (.right, .leftFixed):
+ imageRect.origin.x = contentRect.minX
+ titleRect.origin.x = contentRect.maxX - titleRect.width
+
+ case (.right, .rightFixed):
+ imageRect.origin.x = contentRect.maxX - imageRect.width
+ titleRect.origin.x = imageRect.width > 0
+ ? imageRect.minX - inlineImageSpacing - titleRect.width
+ : contentRect.maxX - titleRect.width
+
+ default:
+ fatalError()
+ }
+
+ return (titleRect, imageRect)
+ }
+
+ override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
+ computeLayout(forContentRect: contentRect).1
+ }
+
+ override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
+ computeLayout(forContentRect: contentRect).0
+ }
+}
diff --git a/ios/MullvadVPN/Views/LinkButton.swift b/ios/MullvadVPN/Views/LinkButton.swift
new file mode 100644
index 0000000000..ebd3be7089
--- /dev/null
+++ b/ios/MullvadVPN/Views/LinkButton.swift
@@ -0,0 +1,58 @@
+//
+// LinkButton.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2023-12-20.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import UIKit
+
+/// 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()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ commonInit()
+ }
+
+ var titleString: String? {
+ didSet {
+ updateAttributedTitle(string: titleString)
+ }
+ }
+
+ private func commonInit() {
+ imageAlignment = .trailing
+ contentHorizontalAlignment = .leading
+
+ accessibilityTraits.insert(.link)
+ }
+
+ private func updateAttributedTitle(string: String?) {
+ let states: [UIControl.State] = [.normal, .highlighted, .disabled]
+ 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 {
+ var attributes: [NSAttributedString.Key: Any] = [
+ .underlineStyle: NSUnderlineStyle.single.rawValue,
+ ]
+
+ if let titleColor = state.customButtonTitleColor {
+ attributes[.foregroundColor] = titleColor
+ }
+
+ return NSAttributedString(string: title, attributes: attributes)
+ }
+}
diff --git a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
index ae815f3692..a12a122772 100644
--- a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
+++ b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
@@ -49,10 +49,13 @@ struct WgAdapter: TunnelAdapterProtocol {
}
private func logIfDeviceHasSameIP(than addresses: [IPAddress]) {
+ let sameIPv4 = IPv4Address("10.127.255.254")
+ let sameIPv6 = IPv6Address("fc00:bbbb:bbbb:bb01:ffff:ffff:ffff:ffff")
+
let hasIPv4SameAddress = addresses.compactMap { $0 as? IPv4Address }
- .contains { $0 == ApplicationConfiguration.sameIPv4 }
+ .contains { $0 == sameIPv4 }
let hasIPv6SameAddress = addresses.compactMap { $0 as? IPv6Address }
- .contains { $0 == ApplicationConfiguration.sameIPv6 }
+ .contains { $0 == sameIPv6 }
let isUsingSameIP = (hasIPv4SameAddress || hasIPv6SameAddress) ? "" : "NOT "
logger.debug("Same IP is \(isUsingSameIP)being used")
diff --git a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
index b80d92df95..1e19bde6c3 100644
--- a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
+++ b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
@@ -214,22 +214,20 @@ final class PacketTunnelActorTests: XCTestCase {
let actor = PacketTunnelActor.mock(blockedStateErrorMapper: blockedStateMapper, settingsReader: settingsReader)
- stateSink = await actor.$observedState
- .receive(on: DispatchQueue.main)
- .sink { newState in
- switch newState {
- case .initial:
- initialStateExpectation.fulfill()
- case .error:
- errorStateExpectation.fulfill()
- case .connecting:
- connectingStateExpectation.fulfill()
- case .connected:
- connectedStateExpectation.fulfill()
- default:
- break
- }
+ stateSink = await actor.$observedState.receive(on: DispatchQueue.main).sink { newState in
+ switch newState {
+ case .initial:
+ initialStateExpectation.fulfill()
+ case .error:
+ errorStateExpectation.fulfill()
+ case .connecting:
+ connectingStateExpectation.fulfill()
+ case .connected:
+ connectedStateExpectation.fulfill()
+ default:
+ break
}
+ }
actor.start(options: launchOptions)
diff --git a/ios/Shared/ApplicationConfiguration.swift b/ios/Shared/ApplicationConfiguration.swift
index a3af1eef75..426067e1dd 100644
--- a/ios/Shared/ApplicationConfiguration.swift
+++ b/ios/Shared/ApplicationConfiguration.swift
@@ -37,10 +37,4 @@ enum ApplicationConfiguration {
/// Maximum number of devices per account.
static let maxAllowedDevices = 5
-
- // FIXME: Used for debugging purposes during the migration to same IP. Remove when the migration is over.
- // swiftlint disable:force_cast
- static let sameIPv4 = IPv4Address("10.127.255.254")!
- static let sameIPv6 = IPv6Address("fc00:bbbb:bbbb:bb01:ffff:ffff:ffff:ffff")!
- // swiftlint enable:force_cast
}