Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor buttons to use UIButton.Configuration #6928

Merged
merged 1 commit into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,6 @@
58FF9FE42B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FE32B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift */; };
58FF9FE82B07650A00E4C97D /* ButtonCellContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FE72B07650A00E4C97D /* ButtonCellContentConfiguration.swift */; };
58FF9FEA2B07653800E4C97D /* ButtonCellContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FE92B07653800E4C97D /* ButtonCellContentView.swift */; };
58FF9FEC2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FEB2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift */; };
58FF9FF02B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FEF2B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift */; };
58FF9FF42B07C61B00E4C97D /* AccessMethodValidationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FF32B07C61B00E4C97D /* AccessMethodValidationError.swift */; };
7A02D4EB2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */; };
Expand Down Expand Up @@ -896,6 +895,8 @@
F06045E62B231EB700B2D37A /* URLSessionTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06045E52B231EB700B2D37A /* URLSessionTransport.swift */; };
F06045EA2B23217E00B2D37A /* ShadowsocksTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06045E92B23217E00B2D37A /* ShadowsocksTransport.swift */; };
F06045EC2B2322A500B2D37A /* Jittered.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06045EB2B2322A500B2D37A /* Jittered.swift */; };
F062000A2CB7EB42002E6DB9 /* CGSize+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06200092CB7EB42002E6DB9 /* CGSize+Helpers.swift */; };
F062000C2CB7EB5D002E6DB9 /* UIImage+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F062000B2CB7EB5D002E6DB9 /* UIImage+Helpers.swift */; };
F062B94D2C16E09700B6D47A /* TunnelSettingsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */; };
F072D3CF2C07122400906F64 /* SettingsUpdaterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3CE2C07122400906F64 /* SettingsUpdaterTests.swift */; };
F072D3D22C071AD100906F64 /* ShadowsocksLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */; };
Expand Down Expand Up @@ -1785,7 +1786,6 @@
58FF9FE32B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccessMethodItemIdentifier.swift; sourceTree = "<group>"; };
58FF9FE72B07650A00E4C97D /* ButtonCellContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCellContentConfiguration.swift; sourceTree = "<group>"; };
58FF9FE92B07653800E4C97D /* ButtonCellContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCellContentView.swift; sourceTree = "<group>"; };
58FF9FEB2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDirectionalEdgeInsets+Helpers.swift"; sourceTree = "<group>"; };
58FF9FEF2B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistentAccessMethod+ViewModel.swift"; sourceTree = "<group>"; };
58FF9FF32B07C61B00E4C97D /* AccessMethodValidationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessMethodValidationError.swift; sourceTree = "<group>"; };
7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNScreenshots.xctestplan; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2117,6 +2117,8 @@
F06045E52B231EB700B2D37A /* URLSessionTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTransport.swift; sourceTree = "<group>"; };
F06045E92B23217E00B2D37A /* ShadowsocksTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsocksTransport.swift; sourceTree = "<group>"; };
F06045EB2B2322A500B2D37A /* Jittered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Jittered.swift; sourceTree = "<group>"; };
F06200092CB7EB42002E6DB9 /* CGSize+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGSize+Helpers.swift"; sourceTree = "<group>"; };
F062000B2CB7EB5D002E6DB9 /* UIImage+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Helpers.swift"; sourceTree = "<group>"; };
F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsManagerTests.swift; sourceTree = "<group>"; };
F072D3CE2C07122400906F64 /* SettingsUpdaterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUpdaterTests.swift; sourceTree = "<group>"; };
F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoaderTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2960,6 +2962,7 @@
isa = PBXGroup;
children = (
5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */,
F06200092CB7EB42002E6DB9 /* CGSize+Helpers.swift */,
587EB669270EFACB00123C75 /* CharacterSet+IPAddress.swift */,
58E511E528DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift */,
7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */,
Expand All @@ -2981,6 +2984,7 @@
5891BF5025E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift */,
587CBFE222807F530028DED3 /* UIColor+Helpers.swift */,
7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */,
F062000B2CB7EB5D002E6DB9 /* UIImage+Helpers.swift */,
58CEB2FA2AFD13E600E6E088 /* UIListContentConfiguration+Extensions.swift */,
58CEB2FC2AFD19D300E6E088 /* UITableView+ReuseIdentifier.swift */,
7A58699A2B482FE200640D27 /* UITableViewCell+Disable.swift */,
Expand Down Expand Up @@ -3016,7 +3020,6 @@
children = (
58CCA0152242560B004F3011 /* UIColor+Palette.swift */,
A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */,
58FF9FEB2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift */,
585CA70E25F8C44600B47C62 /* UIMetrics.swift */,
);
path = "UI appearance";
Expand Down Expand Up @@ -5631,6 +5634,7 @@
587EB672271451E300123C75 /* VPNSettingsViewModel.swift in Sources */,
586A950C290125EE007BAF2B /* AlertPresenter.swift in Sources */,
7A9FA1422A2E3306000B728D /* CheckboxView.swift in Sources */,
F062000A2CB7EB42002E6DB9 /* CGSize+Helpers.swift in Sources */,
586C0D892B03D5E000E7CDD7 /* TextCellContentConfiguration+Extensions.swift in Sources */,
58C3F4F92964B08300D72515 /* MapViewController.swift in Sources */,
584D26C6270C8741004EA533 /* SettingsDNSTextCell.swift in Sources */,
Expand All @@ -5644,6 +5648,7 @@
7A516C2E2B6D357500BBD33D /* URL+Scoping.swift in Sources */,
5878A27529093A310096FC88 /* StorePaymentEvent.swift in Sources */,
7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */,
F062000C2CB7EB5D002E6DB9 /* UIImage+Helpers.swift in Sources */,
7A6389EB2B7FAD7A008E77E1 /* SettingsFieldValidationErrorContentView.swift in Sources */,
58B26E2A2943545A00D5980C /* NotificationManagerDelegate.swift in Sources */,
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */,
Expand Down Expand Up @@ -5840,7 +5845,6 @@
7A5869B92B56E7F000640D27 /* IPOverrideViewControllerDelegate.swift in Sources */,
586C0D7A2B039CE300E7CDD7 /* ShadowsocksCipherPicker.swift in Sources */,
58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */,
58FF9FEC2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift in Sources */,
586C0D832B03D2FF00E7CDD7 /* ShadowsocksSectionHandler.swift in Sources */,
58B26E262943522400D5980C /* NotificationProvider.swift in Sources */,
58CE5E64224146200008646E /* AppDelegate.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ class ButtonCellContentView: UIView, UIContentView {
button.titleLabel?.font = .systemFont(ofSize: 17)
button.isEnabled = actualConfiguration.isEnabled
button.style = actualConfiguration.style
button.overrideContentEdgeInsets = true
button.directionalContentEdgeInsets = actualConfiguration.directionalContentEdgeInsets
button.configuration?.contentInsets = actualConfiguration.directionalContentEdgeInsets
}

private func addSubviews() {
Expand Down
18 changes: 18 additions & 0 deletions ios/MullvadVPN/Extensions/CGSize+Helpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// CGSize+Helpers.swift
// MullvadVPN
//
// Created by Mojgan on 2024-10-10.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import UIKit

extension CGSize {
// Function to deduct insets from CGSize
func deducting(insets: NSDirectionalEdgeInsets) -> CGSize {
let newWidth = width - (insets.leading + insets.trailing)
let newHeight = height - (insets.top + insets.bottom)
return CGSize(width: newWidth, height: newHeight)
}
}
29 changes: 29 additions & 0 deletions ios/MullvadVPN/Extensions/UIImage+Helpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// UIImage+Helpers.swift
// MullvadVPN
//
// Created by Mojgan on 2024-10-10.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import UIKit

extension UIImage {
// Function to resize image while keeping aspect ratio
func resizeImage(targetSize: CGSize) -> UIImage {
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
let scaleFactor = min(widthRatio, heightRatio)

// Calculate new size based on the scale factor
let newSize = CGSize(width: size.width * scaleFactor, height: size.height * scaleFactor)
let renderer = UIGraphicsImageRenderer(size: newSize)

// Render the new image
let resizedImage = renderer.image { _ in
draw(in: CGRect(origin: .zero, size: newSize))
}

return resizedImage.withRenderingMode(renderingMode)
}
}
21 changes: 0 additions & 21 deletions ios/MullvadVPN/UI appearance/NSDirectionalEdgeInsets+Helpers.swift

This file was deleted.

26 changes: 16 additions & 10 deletions ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,35 +92,41 @@ final class AccountInputGroupView: UIView {
}()

private let lastUsedAccountButton: UIButton = {
let button = UIButton(type: .system)
let button = UIButton(configuration: .plain())
button.translatesAutoresizingMaskIntoConstraints = false
button.titleLabel?.font = accountNumberFont()
button.setTitle(" ", for: .normal)
button.contentHorizontalAlignment = .leading
button.contentEdgeInsets = UIMetrics.textFieldMargins
button.setTitleColor(UIColor.AccountTextField.NormalState.textColor, for: .normal)
button.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
button.accessibilityLabel = NSLocalizedString(
"LAST_USED_ACCOUNT_ACCESSIBILITY_LABEL",
tableName: "AccountInput",
value: "Last used account",
comment: ""
)
button.configuration?.contentInsets = UIMetrics.textFieldMargins.toDirectionalInsets
button.configuration?.title = " "
button.configuration?
.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { attributeContainer in
var updatedAttributeContainer = attributeContainer
updatedAttributeContainer.font = AccountInputGroupView.accountNumberFont()
updatedAttributeContainer.foregroundColor = .AccountTextField.NormalState.textColor
return updatedAttributeContainer
}

return button
}()

private let removeLastUsedAccountButton: UIButton = {
let button = UIButton(type: .custom)
private let removeLastUsedAccountButton: CustomButton = {
let button = CustomButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "IconCloseSml"), for: .normal)
button.imageView?.tintColor = .primaryColor.withAlphaComponent(0.4)
button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
button.accessibilityLabel = NSLocalizedString(
"REMOVE_LAST_USED_ACCOUNT_ACCESSIBILITY_LABEL",
tableName: "AccountInput",
value: "Remove last used account",
comment: ""
)
button.configuration?.image = UIImage(resource: .iconCloseSml).withTintColor(.primaryColor)
button.configuration?.title = " "
return button
}()

Expand Down Expand Up @@ -303,7 +309,7 @@ final class AccountInputGroupView: UIView {
)

UIView.performWithoutAnimation {
self.lastUsedAccountButton.setTitle(formattedNumber, for: .normal)
self.lastUsedAccountButton.configuration?.title = formattedNumber
self.lastUsedAccountButton.layoutIfNeeded()
}
}
Expand Down
78 changes: 21 additions & 57 deletions ios/MullvadVPN/View controllers/Tunnel/DisconnectSplitButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,80 +10,44 @@ import Foundation
import UIKit

class DisconnectSplitButton: UIView {
private var secondaryButtonSize: CGSize {
UIMetrics.DisconnectSplitButton.secondaryButton
}

let primaryButton = AppButton(style: .translucentDangerSplitLeft)
let secondaryButton = AppButton(style: .translucentDangerSplitRight)

private let secondaryButtonWidthConstraint: NSLayoutConstraint
private let secondaryButtonHeightConstraint: NSLayoutConstraint
override init(frame: CGRect) {
super.init(frame: .zero)
commonInit()
}

private let stackView: UIStackView
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

init() {
private func commonInit() {
let primaryButtonBlurView = TranslucentButtonBlurView(button: primaryButton)
let secondaryButtonBlurView = TranslucentButtonBlurView(button: secondaryButton)

stackView = UIStackView(arrangedSubviews: [primaryButtonBlurView, secondaryButtonBlurView])
let stackView = UIStackView(arrangedSubviews: [primaryButtonBlurView, secondaryButtonBlurView])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 1

secondaryButton.setImage(
UIImage(named: "IconReload")?.imageFlippedForRightToLeftLayoutDirection(),
for: .normal
)

primaryButton.overrideContentEdgeInsets = true
secondaryButtonWidthConstraint = secondaryButton.widthAnchor.constraint(equalToConstant: 0)
secondaryButtonHeightConstraint = secondaryButton.heightAnchor
.constraint(equalToConstant: 0)

super.init(frame: .zero)

addSubview(stackView)

NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),

secondaryButtonWidthConstraint,
secondaryButtonHeightConstraint,
])

updateTraitConstraints()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private func updateTraitConstraints() {
let newSize = secondaryButtonSize
secondaryButtonWidthConstraint.constant = newSize.width
secondaryButtonHeightConstraint.constant = newSize.height
adjustTitleLabelPosition()
}

private func adjustTitleLabelPosition() {
var contentInsets = primaryButton.defaultContentInsets
let secondaryButtonSize = UIMetrics.DisconnectSplitButton.secondaryButton

let offset = stackView.spacing + secondaryButtonSize.width
addConstrainedSubviews([stackView]) {
stackView.pinEdgesToSuperview()

if case .leftToRight = effectiveUserInterfaceLayoutDirection {
contentInsets.left = offset
contentInsets.right = 0
} else {
contentInsets.left = 0
contentInsets.right = offset
secondaryButton.widthAnchor.constraint(equalToConstant: secondaryButtonSize.width)
secondaryButton.heightAnchor.constraint(equalToConstant: secondaryButtonSize.height)
}

primaryButton.contentEdgeInsets = contentInsets
// Ideally, we shouldn't need to manually resize the image ourselves.
// However, since UIButton.Configuration doesn't provide a direct property
// for controlling image scaling (like imageScaling or contentMode in other contexts),
// manual resizing has been one approach to ensure the image fits within bounds.
secondaryButton.configuration?.image = UIImage(resource: .iconReload)
.resizeImage(targetSize: secondaryButtonSize.deducting(insets: secondaryButton.defaultContentInsets))
.imageFlippedForRightToLeftLayoutDirection()
}
}
Loading
Loading