summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/Containers/Root
diff options
context:
space:
mode:
Diffstat (limited to 'ios/MullvadVPN/Containers/Root')
-rw-r--r--ios/MullvadVPN/Containers/Root/HeaderBarView.swift117
-rw-r--r--ios/MullvadVPN/Containers/Root/RootConfiguration.swift15
-rw-r--r--ios/MullvadVPN/Containers/Root/RootContainerViewController.swift126
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)
}
}