diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2023-04-20 14:09:05 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2023-04-20 14:09:05 +0200 |
| commit | 1e8239d684ed688fcbe6ca5bdbd2e5db319a1c9f (patch) | |
| tree | 05aacca6d953bd99205d73374eb4cb5d5d68e41d /ios/MullvadVPN/Containers/Root | |
| parent | 47ca601dc4c7ae9973bc886fd52f0439d4db7ba1 (diff) | |
| parent | fc907e43ad47737da75520876cfee175c32f9f29 (diff) | |
| download | mullvadvpn-1e8239d684ed688fcbe6ca5bdbd2e5db319a1c9f.tar.xz mullvadvpn-1e8239d684ed688fcbe6ca5bdbd2e5db319a1c9f.zip | |
Merge branch 'add-a-dedicated-account-button-on-the-ios-94'
Diffstat (limited to 'ios/MullvadVPN/Containers/Root')
3 files changed, 175 insertions, 83 deletions
diff --git a/ios/MullvadVPN/Containers/Root/HeaderBarView.swift b/ios/MullvadVPN/Containers/Root/HeaderBarView.swift index efa5e392f9..dcc9cbac92 100644 --- a/ios/MullvadVPN/Containers/Root/HeaderBarView.swift +++ b/ios/MullvadVPN/Containers/Root/HeaderBarView.swift @@ -51,29 +51,43 @@ class HeaderBarView: UIView { return label }() - let settingsButton = makeSettingsButton() - - class func makeSettingsButton() -> HeaderBarButton { - let settingsImage = UIImage(named: "IconSettings")? - .withTintColor(UIColor.HeaderBar.buttonColor, renderingMode: .alwaysOriginal) - let disabledSettingsImage = UIImage(named: "IconSettings")? - .withTintColor( - UIColor.HeaderBar.disabledButtonColor, - renderingMode: .alwaysOriginal - ) + let accountButton: HeaderBarButton = { + let button = makeHeaderBarButton(with: UIImage(named: "IconAccount")) + button.accessibilityIdentifier = "AccountButton" + button.accessibilityLabel = NSLocalizedString( + "HEADER_BAR_ACCOUNT_BUTTON_ACCESSIBILITY_LABEL", + tableName: "HeaderBar", + value: "Account", + comment: "" + ) + return button + }() - let settingsButton = HeaderBarButton(type: .system) - settingsButton.setImage(settingsImage, for: .normal) - settingsButton.setImage(disabledSettingsImage, for: .disabled) - settingsButton.translatesAutoresizingMaskIntoConstraints = false - settingsButton.accessibilityIdentifier = "SettingsButton" - settingsButton.accessibilityLabel = NSLocalizedString( + let settingsButton: HeaderBarButton = { + let button = makeHeaderBarButton(with: UIImage(named: "IconSettings")) + button.accessibilityIdentifier = "SettingsButton" + button.accessibilityLabel = NSLocalizedString( "HEADER_BAR_SETTINGS_BUTTON_ACCESSIBILITY_LABEL", tableName: "HeaderBar", value: "Settings", comment: "" ) - return settingsButton + return button + }() + + class func makeHeaderBarButton(with image: UIImage?) -> HeaderBarButton { + let buttonImage = image?.withTintColor(UIColor.HeaderBar.buttonColor, renderingMode: .alwaysOriginal) + let disabledButtonImage = image?.withTintColor( + UIColor.HeaderBar.disabledButtonColor, + renderingMode: .alwaysOriginal + ) + + let barButton = HeaderBarButton(type: .system) + barButton.setImage(buttonImage, for: .normal) + barButton.setImage(disabledButtonImage, for: .disabled) + barButton.translatesAutoresizingMaskIntoConstraints = false + + return barButton } private let borderLayer: CALayer = { @@ -107,49 +121,52 @@ class HeaderBarView: UIView { let imageSize = brandNameImage?.size ?? .zero let brandNameAspectRatio = imageSize.width / max(imageSize.height, 1) - let constraints = [ - logoImageView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), - logoImageView.centerYAnchor.constraint(equalTo: brandNameImageView.centerYAnchor), - logoImageView.widthAnchor.constraint(equalToConstant: 44), + [deviceName, timeLeft].forEach { deviceInfoHolder.addArrangedSubview($0) } + + addConstrainedSubviews([logoImageView, brandNameImageView, accountButton, settingsButton, deviceInfoHolder]) { + 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 - ), + ) 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), + ) + brandNameImageView.heightAnchor.constraint(equalToConstant: 18) layoutMarginsGuide.bottomAnchor.constraint( equalTo: deviceInfoHolder.bottomAnchor, constant: 8 - ), + ) - settingsButton.leadingAnchor.constraint( + accountButton.leadingAnchor.constraint( greaterThanOrEqualTo: brandNameImageView.trailingAnchor, constant: 8 - ), - settingsButton.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - settingsButton.centerYAnchor.constraint(equalTo: brandNameImageView.centerYAnchor), - - deviceInfoHolder.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), - deviceInfoHolder.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - deviceInfoHolder.topAnchor.constraint(equalTo: logoImageView.bottomAnchor, constant: 7), - ] + ) + accountButton.centerYAnchor.constraint(equalTo: brandNameImageView.centerYAnchor) - [logoImageView, brandNameImageView, settingsButton, deviceInfoHolder].forEach { addSubview($0) } - [deviceName, timeLeft].forEach { deviceInfoHolder.addArrangedSubview($0) } + settingsButton.leadingAnchor.constraint( + equalTo: accountButton.trailingAnchor, + constant: 20 + ).withPriority(.defaultHigh) + settingsButton.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor) + settingsButton.centerYAnchor.constraint(equalTo: accountButton.centerYAnchor) - NSLayoutConstraint.activate(constraints) + deviceInfoHolder.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + deviceInfoHolder.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor) + deviceInfoHolder.topAnchor.constraint(equalTo: logoImageView.bottomAnchor, constant: 7) + } } required init?(coder: NSCoder) { @@ -164,33 +181,35 @@ class HeaderBarView: UIView { } extension HeaderBarView { - func update(deviceState: DeviceState) { - switch deviceState { - case let .loggedIn(storedAccountData, storedDeviceData): + func update(configuration: RootConfigration) { + if let name = configuration.deviceName { let formattedDeviceName = NSLocalizedString( "DEVICE_NAME_HEADER_VIEW", tableName: "Account", - value: "Device name : %@", + value: "Device name: %@", comment: "" ) + deviceName.text = .init(format: formattedDeviceName, name) + } + + if let expiry = configuration.expiry { let formattedTimeLeft = NSLocalizedString( "TIME_LEFT_HEADER_VIEW", tableName: "Account", - value: "Time left : %@", + value: "Time left: %@", comment: "" ) - deviceName.text = .init(format: formattedDeviceName, storedDeviceData.name) timeLeft.text = .init( format: formattedTimeLeft, CustomDateComponentsFormatting.localizedString( from: Date(), - to: storedAccountData.expiry, + to: expiry, unitsStyle: .full ) ?? "" ) - deviceInfoHolder.arrangedSubviews.forEach { $0.isHidden = false } - case .loggedOut, .revoked: - deviceInfoHolder.arrangedSubviews.forEach { $0.isHidden = true } } + + deviceInfoHolder.arrangedSubviews.forEach { $0.isHidden = configuration.deviceName == nil } + accountButton.isHidden = !configuration.showsAccountButton } } diff --git a/ios/MullvadVPN/Containers/Root/RootConfiguration.swift b/ios/MullvadVPN/Containers/Root/RootConfiguration.swift new file mode 100644 index 0000000000..2229bdf4a0 --- /dev/null +++ b/ios/MullvadVPN/Containers/Root/RootConfiguration.swift @@ -0,0 +1,15 @@ +// +// RootConfiguration.swift +// MullvadVPN +// +// Created by Jon Petersson on 2023-04-19. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +struct RootConfigration { + var deviceName: String? + var expiry: Date? + var showsAccountButton: Bool +} diff --git a/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift b/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift index 3b96728d12..679964d46c 100644 --- a/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift +++ b/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift @@ -44,6 +44,11 @@ protocol RootContainment { } protocol RootContainerViewControllerDelegate: AnyObject { + func rootContainerViewControllerShouldShowAccount( + _ controller: RootContainerViewController, + animated: Bool + ) + func rootContainerViewControllerShouldShowSettings( _ controller: RootContainerViewController, navigateTo route: SettingsNavigationRoute?, @@ -62,8 +67,10 @@ class RootContainerViewController: UIViewController { typealias CompletionHandler = () -> Void private let headerBarView = HeaderBarView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) - private let transitionContainer = UIView(frame: UIScreen.main.bounds) + let transitionContainer = UIView(frame: UIScreen.main.bounds) + private var presentationContainerAccountButton: UIButton? private var presentationContainerSettingsButton: UIButton? + private var configuration = RootConfigration(showsAccountButton: false) private(set) var headerBarPresentation = HeaderBarPresentation.default private(set) var headerBarHidden = false @@ -256,6 +263,14 @@ class RootContainerViewController: UIViewController { } /// Request to display settings controller + func showAccount(animated: Bool) { + delegate?.rootContainerViewControllerShouldShowAccount( + self, + animated: animated + ) + } + + /// Request to display settings controller func showSettings(navigateTo route: SettingsNavigationRoute? = nil, animated: Bool) { delegate?.rootContainerViewControllerShouldShowSettings( self, @@ -264,47 +279,37 @@ class RootContainerViewController: UIViewController { ) } - /// Enable or disable the settings bar button displayed in the header bar - func setEnableSettingsButton(_ isEnabled: Bool) { - headerBarView.settingsButton.isEnabled = isEnabled - presentationContainerSettingsButton?.isEnabled = isEnabled - } - - /// Add settings bar button into the presentation container to make settings accessible even - /// when the root container is covered with modal. - func addSettingsButtonToPresentationContainer(_ presentationContainer: UIView) { - let settingsButton: UIButton + /// Add account and settings bar buttons into the presentation container to make them accessible even + /// when the root container is covered with a modal. + func addTrailingButtonsToPresentationContainer(_ presentationContainer: UIView) { + let accountButton = getPresentationContainerAccountButton() + let settingsButton = getPresentationContainerSettingsButton() - if let transitionViewSettingsButton = presentationContainerSettingsButton { - transitionViewSettingsButton.removeFromSuperview() - settingsButton = transitionViewSettingsButton - } else { - settingsButton = HeaderBarView.makeSettingsButton() - settingsButton.isEnabled = headerBarView.settingsButton.isEnabled - settingsButton.addTarget( - self, - action: #selector(handleSettingsButtonTap), - for: .touchUpInside - ) - - presentationContainerSettingsButton = settingsButton - } + presentationContainerAccountButton = accountButton + presentationContainerSettingsButton = settingsButton - // Hide the settings button inside the header bar to avoid color blending issues + // Hide the account button inside the header bar to avoid color blending issues + headerBarView.accountButton.alpha = 0 headerBarView.settingsButton.alpha = 0 - presentationContainer.addSubview(settingsButton) + presentationContainer.addConstrainedSubviews([accountButton, settingsButton]) { + accountButton.centerXAnchor + .constraint(equalTo: headerBarView.accountButton.centerXAnchor) + accountButton.centerYAnchor + .constraint(equalTo: headerBarView.accountButton.centerYAnchor) - NSLayoutConstraint.activate([ settingsButton.centerXAnchor - .constraint(equalTo: headerBarView.settingsButton.centerXAnchor), + .constraint(equalTo: headerBarView.settingsButton.centerXAnchor) settingsButton.centerYAnchor - .constraint(equalTo: headerBarView.settingsButton.centerYAnchor), - ]) + .constraint(equalTo: headerBarView.settingsButton.centerYAnchor) + } } - func removeSettingsButtonFromPresentationContainer() { + func removeTrailingButtonsFromPresentationContainer() { + presentationContainerAccountButton?.removeFromSuperview() presentationContainerSettingsButton?.removeFromSuperview() + + headerBarView.accountButton.alpha = 1 headerBarView.settingsButton.alpha = 1 } @@ -353,6 +358,12 @@ class RootContainerViewController: UIViewController { // Prevent automatic layout margins adjustment as we manually control them. headerBarView.insetsLayoutMarginsFromSafeArea = false + headerBarView.accountButton.addTarget( + self, + action: #selector(handleAccountButtonTap), + for: .touchUpInside + ) + headerBarView.settingsButton.addTarget( self, action: #selector(handleSettingsButtonTap), @@ -364,6 +375,50 @@ class RootContainerViewController: UIViewController { NSLayoutConstraint.activate(constraints) } + private func getPresentationContainerAccountButton() -> UIButton { + let button: UIButton + + if let transitionViewButton = presentationContainerAccountButton { + transitionViewButton.removeFromSuperview() + button = transitionViewButton + } else { + button = HeaderBarView.makeHeaderBarButton(with: UIImage(named: "IconAccount")) + button.addTarget( + self, + action: #selector(handleAccountButtonTap), + for: .touchUpInside + ) + } + + button.isEnabled = headerBarView.accountButton.isEnabled + button.isHidden = !configuration.showsAccountButton + + return button + } + + private func getPresentationContainerSettingsButton() -> UIButton { + let button: UIButton + + if let transitionViewButton = presentationContainerSettingsButton { + transitionViewButton.removeFromSuperview() + button = transitionViewButton + } else { + button = HeaderBarView.makeHeaderBarButton(with: UIImage(named: "IconSettings")) + button.isEnabled = headerBarView.settingsButton.isEnabled + button.addTarget( + self, + action: #selector(handleSettingsButtonTap), + for: .touchUpInside + ) + } + + return button + } + + @objc private func handleAccountButtonTap() { + showAccount(animated: true) + } + @objc private func handleSettingsButtonTap() { showSettings(animated: true) } @@ -676,7 +731,10 @@ extension UIViewController { } extension RootContainerViewController { - func update(deviceState: DeviceState) { - headerBarView.update(deviceState: deviceState) + func update(configuration: RootConfigration) { + self.configuration = configuration + + presentationContainerAccountButton?.isHidden = !configuration.showsAccountButton + headerBarView.update(configuration: configuration) } } |
