summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gui/assets/images/icon-account.svg3
-rw-r--r--ios/CHANGELOG.md1
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj31
-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
-rw-r--r--ios/MullvadVPN/Coordinators/App/AccountCoordinator.swift58
-rw-r--r--ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift66
-rw-r--r--ios/MullvadVPN/Coordinators/App/ApplicationRouter.swift18
-rw-r--r--ios/MullvadVPN/Coordinators/App/SettingsCoordinator.swift27
-rw-r--r--ios/MullvadVPN/Presentation controllers/SecondaryContextPresentationController.swift4
-rw-r--r--ios/MullvadVPN/SceneDelegate.swift2
-rw-r--r--ios/MullvadVPN/Supporting Files/Assets.xcassets/IconAccount.imageset/Contents.json15
-rw-r--r--ios/MullvadVPN/Supporting Files/Assets.xcassets/IconAccount.imageset/IconAccount.pdfbin0 -> 1408 bytes
-rw-r--r--ios/MullvadVPN/Supporting Files/Assets.xcassets/IconSettings.imageset/IconSettings.pdfbin1399 -> 1399 bytes
-rw-r--r--ios/MullvadVPN/View controllers/Account/AccountViewController.swift11
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsAccountCell.swift54
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift13
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift48
-rw-r--r--ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift2
-rwxr-xr-xios/convert-assets.rb7
21 files changed, 364 insertions, 254 deletions
diff --git a/gui/assets/images/icon-account.svg b/gui/assets/images/icon-account.svg
new file mode 100644
index 0000000000..0c6b3faaf1
--- /dev/null
+++ b/gui/assets/images/icon-account.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 24A12 12 0 0 1 3.515 3.515a12 12 0 1 1 16.97 16.97A11.922 11.922 0 0 1 12 24zm0-11.825a12.164 12.164 0 0 0-2.873.348 17.625 17.625 0 0 0-2.99 1.048A2.85 2.85 0 0 0 5 14.525a2.573 2.573 0 0 0-.442 1.512v.791a1.39 1.39 0 0 0 1.4 1.4h12.1a1.392 1.392 0 0 0 1.4-1.4v-.791A2.567 2.567 0 0 0 19 14.525a2.809 2.809 0 0 0-1.163-.954 19.906 19.906 0 0 0-2.978-1.036 11.634 11.634 0 0 0-2.859-.36zm0-8.4a3.345 3.345 0 0 0-3.49 3.491 3.346 3.346 0 0 0 3.49 3.49 3.348 3.348 0 0 0 3.49-3.49A3.346 3.346 0 0 0 12 3.776z" style="fill:#294d73"/>
+</svg>
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 582f230db5..6a1a8b61f0 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -26,6 +26,7 @@ Line wrap the file at 100 chars. Th
### Added
- Add search functionality to location selection view.
- Wipe all settings on app reinstall.
+- Add a dedicated account button on the main view and remove it from settings.
### Changed
- Changed key rotation interval from 4 to 14 days.
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 83c283dd5c..3180a3d59e 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -87,7 +87,6 @@
582AE3122440CA0D00E6733A /* AccountTokenInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE3112440CA0D00E6733A /* AccountTokenInputTests.swift */; };
582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1AE229566420055B6EF /* SettingsCell.swift */; };
582BB1B1229569620055B6EF /* UINavigationBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B0229569620055B6EF /* UINavigationBar+Appearance.swift */; };
- 582BB1B3229574F40055B6EF /* SettingsAccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */; };
5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5835B7CB233B76CB0096D79F /* TunnelManager.swift */; };
5838318B27C40A3900000571 /* Pinger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838318A27C40A3900000571 /* Pinger.swift */; };
583DA21425FA4B5C00318683 /* LocationDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583DA21325FA4B5C00318683 /* LocationDataSource.swift */; };
@@ -360,8 +359,10 @@
58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */; };
58FF2C03281BDE02009EF542 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF2C02281BDE02009EF542 /* SettingsManager.swift */; };
7A09C98129D99215000C2CAC /* String+FuzzyMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */; };
- 7AD2DA1529DC4EB900250737 /* UISearchBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD2DA1429DC4EB900250737 /* UISearchBar+Appearance.swift */; };
7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */; };
+ 7A818F1F29F0305800C7F0F4 /* RootConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */; };
+ 7AD2DA1529DC4EB900250737 /* UISearchBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD2DA1429DC4EB900250737 /* UISearchBar+Appearance.swift */; };
+ 7AF0419E29E957EB00D492DD /* AccountCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */; };
E1187ABC289BBB850024E748 /* OutOfTimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */; };
E1187ABD289BBB850024E748 /* OutOfTimeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */; };
E158B360285381C60002F069 /* String+AccountFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* String+AccountFormatting.swift */; };
@@ -705,7 +706,6 @@
582AE3112440CA0D00E6733A /* AccountTokenInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTokenInputTests.swift; sourceTree = "<group>"; };
582BB1AE229566420055B6EF /* SettingsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = "<group>"; };
582BB1B0229569620055B6EF /* UINavigationBar+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Appearance.swift"; sourceTree = "<group>"; };
- 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountCell.swift; sourceTree = "<group>"; };
582FFA82290A84E700895745 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
5835B7CB233B76CB0096D79F /* TunnelManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManager.swift; sourceTree = "<group>"; };
5838318A27C40A3900000571 /* Pinger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pinger.swift; sourceTree = "<group>"; };
@@ -945,11 +945,10 @@
58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomaticKeyboardResponder.swift; sourceTree = "<group>"; };
58FF2C02281BDE02009EF542 /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = "<group>"; };
7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+FuzzyMatch.swift"; sourceTree = "<group>"; };
- 7AD2DA1429DC4EB900250737 /* UISearchBar+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchBar+Appearance.swift"; sourceTree = "<group>"; };
7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimeLaunch.swift; sourceTree = "<group>"; };
- 7AD8490C29BA1EC500878E53 /* SettingsCellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCellFactory.swift; sourceTree = "<group>"; };
- 7AD8490E29BA26B000878E53 /* CellFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellFactoryProtocol.swift; sourceTree = "<group>"; };
- 7AD8491029BA316500878E53 /* PreferencesCellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesCellFactory.swift; sourceTree = "<group>"; };
+ 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootConfiguration.swift; sourceTree = "<group>"; };
+ 7AD2DA1429DC4EB900250737 /* UISearchBar+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchBar+Appearance.swift"; sourceTree = "<group>"; };
+ 7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCoordinator.swift; sourceTree = "<group>"; };
E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeViewController.swift; sourceTree = "<group>"; };
E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeContentView.swift; sourceTree = "<group>"; };
E158B35F285381C60002F069 /* String+AccountFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+AccountFormatting.swift"; sourceTree = "<group>"; };
@@ -1282,7 +1281,6 @@
5867770F290975E8006F721F /* SettingsInteractorFactory.swift */,
58677711290976FB006F721F /* SettingsInteractor.swift */,
582BB1AE229566420055B6EF /* SettingsCell.swift */,
- 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */,
584D26C1270C8542004EA533 /* SettingsStaticTextFooterView.swift */,
58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */,
5819C2162729595500D6EC38 /* SettingsAddDNSEntryCell.swift */,
@@ -1585,17 +1583,18 @@
589453E0297807DB0015DA3B /* App */ = {
isa = PBXGroup;
children = (
- 5893C6FB29C311E9009090D1 /* ApplicationRouter.swift */,
+ 7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */,
58BBB39629717E0C00C8DB7C /* ApplicationCoordinator.swift */,
+ 5893C6FB29C311E9009090D1 /* ApplicationRouter.swift */,
+ 5878F50129CDB989003D4BE2 /* ChangeLogCoordinator.swift */,
58CAF9F92983E0C600BE19F7 /* LoginCoordinator.swift */,
+ 583FE00D29C0D586006E85F9 /* OutOfTimeCoordinator.swift */,
+ 5847D58C29B7740F008C3808 /* RevokedCoordinator.swift */,
+ 586891CC29D452E4002A8278 /* SafariCoordinator.swift */,
587C92FD2986E28100FB9664 /* SelectLocationCoordinator.swift */,
+ 58C3F4FA296C3AD500D72515 /* SettingsCoordinator.swift */,
587C92FF2986E2B600FB9664 /* TermsOfServiceCoordinator.swift */,
58F185A9298A3E3E00075977 /* TunnelCoordinator.swift */,
- 58C3F4FA296C3AD500D72515 /* SettingsCoordinator.swift */,
- 5847D58C29B7740F008C3808 /* RevokedCoordinator.swift */,
- 583FE00D29C0D586006E85F9 /* OutOfTimeCoordinator.swift */,
- 5878F50129CDB989003D4BE2 /* ChangeLogCoordinator.swift */,
- 586891CC29D452E4002A8278 /* SafariCoordinator.swift */,
);
path = App;
sourceTree = "<group>";
@@ -1814,6 +1813,7 @@
587425C02299833500CA2045 /* RootContainerViewController.swift */,
58B3F30E2742708B00A2DD38 /* HeaderBarButton.swift */,
58F3C0A3249CB069003E76BE /* HeaderBarView.swift */,
+ 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */,
);
path = Root;
sourceTree = "<group>";
@@ -2589,6 +2589,7 @@
587B75412668FD7800DEF7E9 /* AccountExpirySystemNotificationProvider.swift in Sources */,
587988C728A2A01F00E3DF54 /* AccountDataThrottling.swift in Sources */,
5896CEF226972DEB00B0FAE8 /* AccountContentView.swift in Sources */,
+ 7AF0419E29E957EB00D492DD /* AccountCoordinator.swift in Sources */,
5867771429097BCD006F721F /* PaymentState.swift in Sources */,
587D96742886D87C00CD8F1C /* DeviceManagementContentView.swift in Sources */,
589A454C28DDF5E100565204 /* Swizzle.swift in Sources */,
@@ -2612,7 +2613,6 @@
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */,
5878A27B2909649A0096FC88 /* CustomOverlayRenderer.swift in Sources */,
5847D58D29B7740F008C3808 /* RevokedCoordinator.swift in Sources */,
- 582BB1B3229574F40055B6EF /* SettingsAccountCell.swift in Sources */,
588527B2276B3F0700BAA373 /* LoadTunnelConfigurationOperation.swift in Sources */,
58F1311527E0B2AB007AC5BC /* Result+Extensions.swift in Sources */,
5867770E29096984006F721F /* OutOfTimeInteractor.swift in Sources */,
@@ -2652,6 +2652,7 @@
58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */,
58138E61294871C600684F0C /* DeviceDataThrottling.swift in Sources */,
5878A279290954790096FC88 /* TunnelViewControllerInteractor.swift in Sources */,
+ 7A818F1F29F0305800C7F0F4 /* RootConfiguration.swift in Sources */,
582AE3102440A6CA00E6733A /* AccountTokenInput.swift in Sources */,
5820EDAB288FF0D2006BF4E4 /* DeviceRowView.swift in Sources */,
5846227726E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift in Sources */,
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)
}
}
diff --git a/ios/MullvadVPN/Coordinators/App/AccountCoordinator.swift b/ios/MullvadVPN/Coordinators/App/AccountCoordinator.swift
new file mode 100644
index 0000000000..13ca7bcb5d
--- /dev/null
+++ b/ios/MullvadVPN/Coordinators/App/AccountCoordinator.swift
@@ -0,0 +1,58 @@
+//
+// AccountCoordinator.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2023-04-14.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import UIKit
+
+enum AccountDismissReason: Equatable {
+ case none
+ case userLoggedOut
+}
+
+final class AccountCoordinator: Coordinator, Presentable, Presenting {
+ private let interactor: AccountInteractor
+ private var accountController: AccountViewController?
+
+ let navigationController: UINavigationController
+ var presentedViewController: UIViewController {
+ return navigationController
+ }
+
+ var presentationContext: UIViewController {
+ return navigationController
+ }
+
+ var didFinish: ((AccountCoordinator, AccountDismissReason) -> Void)?
+
+ init(
+ navigationController: UINavigationController,
+ interactor: AccountInteractor
+ ) {
+ self.navigationController = navigationController
+ self.interactor = interactor
+ }
+
+ func start(animated: Bool) {
+ navigationController.navigationBar.prefersLargeTitles = true
+
+ let accountController = AccountViewController(interactor: interactor)
+ accountController.delegate = self
+
+ navigationController.pushViewController(accountController, animated: animated)
+ self.accountController = accountController
+ }
+}
+
+extension AccountCoordinator: AccountViewControllerDelegate {
+ func accountViewControllerDidFinish(_ controller: AccountViewController) {
+ didFinish?(self, .none)
+ }
+
+ func accountViewControllerDidLogout(_ controller: AccountViewController) {
+ didFinish?(self, .userLoggedOut)
+ }
+}
diff --git a/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
index 710b0e2cdc..fa6e879240 100644
--- a/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
@@ -123,6 +123,9 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
completion: @escaping (Coordinator) -> Void
) {
switch route {
+ case .account:
+ presentAccount(animated: animated, completion: completion)
+
case let .settings(subRoute):
presentSettings(route: subRoute, animated: animated, completion: completion)
@@ -162,7 +165,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
endHorizontalFlow(animated: context.isAnimated, completion: completion)
context.dismissedRoutes.forEach { $0.coordinator.removeFromParent() }
- case .selectLocation, .settings:
+ case .selectLocation, .account, .settings:
let coordinator = dismissedRoute.coordinator as! Presentable
coordinator.dismiss(animated: context.isAnimated, completion: completion)
@@ -292,9 +295,9 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
}
}
- private func didDismissSettings(_ reason: SettingsDismissReason) {
+ private func didDismissAccount(_ reason: AccountDismissReason) {
if isPad {
- router.dismissAll(.settings, animated: true)
+ router.dismiss(.account, animated: true)
if reason == .userLoggedOut {
router.dismissAll(.primary, animated: true)
@@ -306,7 +309,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
continueFlow(animated: false)
}
- router.dismissAll(.settings, animated: true)
+ router.dismiss(.account, animated: true)
}
}
@@ -541,6 +544,35 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
return selectLocationCoordinator
}
+ private func presentAccount(animated: Bool, completion: @escaping (Coordinator) -> Void) {
+ let accountInteractor = AccountInteractor(
+ storePaymentManager: storePaymentManager,
+ tunnelManager: tunnelManager
+ )
+
+ let coordinator = AccountCoordinator(
+ navigationController: CustomNavigationController(),
+ interactor: accountInteractor
+ )
+
+ coordinator.didFinish = { [weak self] coordinator, reason in
+ self?.didDismissAccount(reason)
+ }
+
+ coordinator.start(animated: animated)
+
+ presentChild(
+ coordinator,
+ animated: animated,
+ configuration: ModalPresentationConfiguration(
+ preferredContentSize: preferredFormSheetContentSize,
+ modalPresentationStyle: .formSheet
+ )
+ ) {
+ completion(coordinator)
+ }
+ }
+
private func presentSettings(
route: SettingsNavigationRoute?,
animated: Bool,
@@ -558,8 +590,8 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
interactorFactory: interactorFactory
)
- coordinator.didFinish = { [weak self] coordinator, reason in
- self?.didDismissSettings(reason)
+ coordinator.didFinish = { [weak self] coordinator in
+ self?.router.dismissAll(.settings, animated: true)
}
coordinator.willNavigate = { [weak self] coordinator, from, to in
@@ -608,7 +640,6 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
if !accountData.isExpired {
router.dismiss(.outOfTime, animated: true)
}
-
case .revoked:
cancelOutOfTimeTimer()
router.present(.revoked, animated: true)
@@ -619,8 +650,14 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
}
private func updateView(deviceState: DeviceState) {
- primaryNavigationContainer.update(deviceState: deviceState)
- secondaryNavigationContainer.update(deviceState: deviceState)
+ let configuration = RootConfigration(
+ deviceName: deviceState.deviceData?.name,
+ expiry: deviceState.accountData?.expiry,
+ showsAccountButton: deviceState.isLoggedIn
+ )
+
+ primaryNavigationContainer.update(configuration: configuration)
+ secondaryNavigationContainer.update(configuration: configuration)
}
// MARK: - Out of time
@@ -658,8 +695,8 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
// MARK: - Deep link
- func showAccountSettings() {
- router.present(.settings(.account))
+ func showAccount() {
+ router.present(.account)
}
// MARK: - UISplitViewControllerDelegate
@@ -697,6 +734,13 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
// MARK: - RootContainerViewControllerDelegate
+ func rootContainerViewControllerShouldShowAccount(
+ _ controller: RootContainerViewController,
+ animated: Bool
+ ) {
+ router.present(.account, animated: animated)
+ }
+
func rootContainerViewControllerShouldShowSettings(
_ controller: RootContainerViewController,
navigateTo route: SettingsNavigationRoute?,
diff --git a/ios/MullvadVPN/Coordinators/App/ApplicationRouter.swift b/ios/MullvadVPN/Coordinators/App/ApplicationRouter.swift
index a6277dba58..663de96b07 100644
--- a/ios/MullvadVPN/Coordinators/App/ApplicationRouter.swift
+++ b/ios/MullvadVPN/Coordinators/App/ApplicationRouter.swift
@@ -26,6 +26,11 @@ enum AppRouteGroup: Comparable, Equatable, Hashable {
case selectLocation
/**
+ Account group.
+ */
+ case account
+
+ /**
Settings group.
*/
case settings
@@ -39,7 +44,7 @@ enum AppRouteGroup: Comparable, Equatable, Hashable {
case .primary:
return UIDevice.current.userInterfaceIdiom == .pad
- case .selectLocation, .settings:
+ case .selectLocation, .account, .settings:
return true
}
}
@@ -48,7 +53,7 @@ enum AppRouteGroup: Comparable, Equatable, Hashable {
switch self {
case .primary:
return 0
- case .settings, .selectLocation:
+ case .settings, .account, .selectLocation:
return 1
}
}
@@ -63,6 +68,11 @@ enum AppRouteGroup: Comparable, Equatable, Hashable {
*/
enum AppRoute: Equatable, Hashable {
/**
+ Account route.
+ */
+ case account
+
+ /**
Settings route. Contains sub-route to display.
*/
case settings(SettingsNavigationRoute?)
@@ -82,7 +92,7 @@ enum AppRoute: Equatable, Hashable {
*/
var isExclusive: Bool {
switch self {
- case .selectLocation, .settings:
+ case .selectLocation, .account, .settings:
return true
default:
return false
@@ -109,6 +119,8 @@ enum AppRoute: Equatable, Hashable {
return .primary
case .selectLocation:
return .selectLocation
+ case .account:
+ return .account
case .settings:
return .settings
}
diff --git a/ios/MullvadVPN/Coordinators/App/SettingsCoordinator.swift b/ios/MullvadVPN/Coordinators/App/SettingsCoordinator.swift
index 89fffaf2c7..e6b7f232a4 100644
--- a/ios/MullvadVPN/Coordinators/App/SettingsCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/App/SettingsCoordinator.swift
@@ -12,19 +12,13 @@ import UIKit
enum SettingsNavigationRoute: Equatable {
case root
- case account
case preferences
case problemReport
case faq
}
-enum SettingsDismissReason: Equatable {
- case none
- case userLoggedOut
-}
-
final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsViewControllerDelegate,
- AccountViewControllerDelegate, UINavigationControllerDelegate
+ UINavigationControllerDelegate
{
private let logger = Logger(label: "SettingsNavigationCoordinator")
@@ -48,7 +42,7 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
_ to: SettingsNavigationRoute
) -> Void)?
- var didFinish: ((SettingsCoordinator, SettingsDismissReason) -> Void)?
+ var didFinish: ((SettingsCoordinator) -> Void)?
init(
navigationController: UINavigationController,
@@ -137,7 +131,7 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
// MARK: - SettingsViewControllerDelegate
func settingsViewControllerDidFinish(_ controller: SettingsViewController) {
- didFinish?(self, .none)
+ didFinish?(self)
}
func settingsViewController(
@@ -147,12 +141,6 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
navigate(to: route, animated: true)
}
- // MARK: - AccountViewControllerDelegate
-
- func accountViewControllerDidLogout(_ controller: AccountViewController) {
- didFinish?(self, .userLoggedOut)
- }
-
// MARK: - Route mapping
private func makeViewController(for route: SettingsNavigationRoute) -> UIViewController? {
@@ -164,13 +152,6 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
controller.delegate = self
return controller
- case .account:
- let controller = AccountViewController(
- interactor: interactorFactory.makeAccountInteractor()
- )
- controller.delegate = self
- return controller
-
case .preferences:
return PreferencesViewController(
interactor: interactorFactory.makePreferencesInteractor()
@@ -190,8 +171,6 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
switch viewController {
case is SettingsViewController:
return .root
- case is AccountViewController:
- return .account
case is PreferencesViewController:
return .preferences
case is ProblemReportViewController:
diff --git a/ios/MullvadVPN/Presentation controllers/SecondaryContextPresentationController.swift b/ios/MullvadVPN/Presentation controllers/SecondaryContextPresentationController.swift
index 6a0397efa2..6324bf3e31 100644
--- a/ios/MullvadVPN/Presentation controllers/SecondaryContextPresentationController.swift
+++ b/ios/MullvadVPN/Presentation controllers/SecondaryContextPresentationController.swift
@@ -21,7 +21,7 @@ class SecondaryContextPresentationController: FormsheetPresentationController {
if let containerView = containerView,
let rootContainer = presentingViewController as? RootContainerViewController
{
- rootContainer.addSettingsButtonToPresentationContainer(containerView)
+ rootContainer.addTrailingButtonsToPresentationContainer(containerView)
}
}
@@ -29,7 +29,7 @@ class SecondaryContextPresentationController: FormsheetPresentationController {
super.dismissalTransitionDidEnd(completed)
if let rootContainer = presentingViewController as? RootContainerViewController, completed {
- rootContainer.removeSettingsButtonFromPresentationContainer()
+ rootContainer.removeTrailingButtonsFromPresentationContainer()
}
}
diff --git a/ios/MullvadVPN/SceneDelegate.swift b/ios/MullvadVPN/SceneDelegate.swift
index 689d904be8..5a6651e594 100644
--- a/ios/MullvadVPN/SceneDelegate.swift
+++ b/ios/MullvadVPN/SceneDelegate.swift
@@ -37,7 +37,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate, SettingsMigrationUIHand
// MARK: - Deep link
func showUserAccount() {
- appCoordinator?.showAccountSettings()
+ appCoordinator?.showAccount()
}
// MARK: - Private
diff --git a/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconAccount.imageset/Contents.json b/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconAccount.imageset/Contents.json
new file mode 100644
index 0000000000..573b102f42
--- /dev/null
+++ b/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconAccount.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "IconAccount.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconAccount.imageset/IconAccount.pdf b/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconAccount.imageset/IconAccount.pdf
new file mode 100644
index 0000000000..1d57a67c83
--- /dev/null
+++ b/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconAccount.imageset/IconAccount.pdf
Binary files differ
diff --git a/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconSettings.imageset/IconSettings.pdf b/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconSettings.imageset/IconSettings.pdf
index 3f2ef14157..1efefb905b 100644
--- a/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconSettings.imageset/IconSettings.pdf
+++ b/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconSettings.imageset/IconSettings.pdf
Binary files differ
diff --git a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
index 34f16b3233..a7c4beb867 100644
--- a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
+++ b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift
@@ -14,6 +14,7 @@ import StoreKit
import UIKit
protocol AccountViewControllerDelegate: AnyObject {
+ func accountViewControllerDidFinish(_ controller: AccountViewController)
func accountViewControllerDidLogout(_ controller: AccountViewController)
}
@@ -79,6 +80,12 @@ class AccountViewController: UIViewController {
comment: ""
)
+ navigationItem.rightBarButtonItem = UIBarButtonItem(
+ barButtonSystemItem: .done,
+ target: self,
+ action: #selector(handleDismiss)
+ )
+
contentView.accountTokenRowView.copyAccountNumber = { [weak self] in
self?.copyAccountToken()
}
@@ -115,6 +122,10 @@ class AccountViewController: UIViewController {
// MARK: - Private
+ @objc private func handleDismiss() {
+ delegate?.accountViewControllerDidFinish(self)
+ }
+
private func requestStoreProducts() {
let productKind = StoreSubscription.thirtyDays
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsAccountCell.swift b/ios/MullvadVPN/View controllers/Settings/SettingsAccountCell.swift
deleted file mode 100644
index f0621f31dc..0000000000
--- a/ios/MullvadVPN/View controllers/Settings/SettingsAccountCell.swift
+++ /dev/null
@@ -1,54 +0,0 @@
-//
-// SettingsAccountCell.swift
-// MullvadVPN
-//
-// Created by pronebird on 22/05/2019.
-// Copyright © 2019 Mullvad VPN AB. All rights reserved.
-//
-
-import UIKit
-
-class SettingsAccountCell: SettingsCell {
- var accountExpiryDate: Date? {
- didSet {
- didUpdateAccountExpiry()
- }
- }
-
- private func didUpdateAccountExpiry() {
- guard let accountExpiryDate = accountExpiryDate else {
- detailTitleLabel.text = ""
- detailTitleLabel.textColor = UIColor.Cell.detailTextColor
- return
- }
-
- guard accountExpiryDate > Date() else {
- detailTitleLabel.text = NSLocalizedString(
- "ACCOUNT_CELL_OUT_OF_TIME_LABEL",
- tableName: "Settings",
- value: "OUT OF TIME",
- comment: ""
- )
- detailTitleLabel.textColor = .dangerColor
- return
- }
-
- let formattedTime = CustomDateComponentsFormatting.localizedString(
- from: Date(),
- to: accountExpiryDate,
- unitsStyle: .full
- )
-
- detailTitleLabel.text = formattedTime.map { remainingTimeString in
- let localizedString = NSLocalizedString(
- "ACCOUNT_CELL_TIME_LEFT_LABEL_FORMAT",
- tableName: "Settings",
- value: "%@ left",
- comment: ""
- )
-
- return String(format: localizedString, remainingTimeString).uppercased()
- } ?? ""
- detailTitleLabel.textColor = UIColor.Cell.detailTextColor
- }
-}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
index 4aef9175bc..3bb4440fa1 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsCellFactory.swift
@@ -27,19 +27,6 @@ struct SettingsCellFactory: CellFactoryProtocol {
func configureCell(_ cell: UITableViewCell, item: SettingsDataSource.Item, indexPath: IndexPath) {
switch item {
- case .account:
- guard let cell = cell as? SettingsAccountCell else { return }
-
- cell.titleLabel.text = NSLocalizedString(
- "ACCOUNT_CELL_LABEL",
- tableName: "Settings",
- value: "Account",
- comment: ""
- )
- cell.accountExpiryDate = interactor.deviceState.accountData?.expiry
- cell.accessibilityIdentifier = "AccountCell"
- cell.disclosureType = .chevron
-
case .preferences:
guard let cell = cell as? SettingsCell else { return }
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift b/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
index cc460e596b..954361c463 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsDataSource.swift
@@ -13,16 +13,10 @@ final class SettingsDataSource: UITableViewDiffableDataSource<
SettingsDataSource.Item
>, UITableViewDelegate {
enum CellReuseIdentifiers: String, CaseIterable {
- case accountCell
case basicCell
var reusableViewClass: AnyClass {
- switch self {
- case .accountCell:
- return SettingsAccountCell.self
- case .basicCell:
- return SettingsCell.self
- }
+ return SettingsCell.self
}
}
@@ -30,10 +24,7 @@ final class SettingsDataSource: UITableViewDiffableDataSource<
case spacer
var reusableViewClass: AnyClass {
- switch self {
- case .spacer:
- return EmptyTableViewHeaderFooterView.self
- }
+ return EmptyTableViewHeaderFooterView.self
}
}
@@ -44,19 +35,13 @@ final class SettingsDataSource: UITableViewDiffableDataSource<
}
enum Item: String {
- case account
case preferences
case version
case problemReport
case faq
var reuseIdentifier: CellReuseIdentifiers {
- switch self {
- case .account:
- return .accountCell
- default:
- return .basicCell
- }
+ return .basicCell
}
}
@@ -83,7 +68,7 @@ final class SettingsDataSource: UITableViewDiffableDataSource<
updateDataSnapshot()
interactor.didUpdateDeviceState = { [weak self] deviceState in
- self?.didUpdateDeviceState(deviceState)
+ self?.updateDataSnapshot()
}
storedAccountData = interactor.deviceState.accountData
}
@@ -145,7 +130,7 @@ final class SettingsDataSource: UITableViewDiffableDataSource<
if interactor.deviceState.isLoggedIn {
snapshot.appendSections([.main])
- snapshot.appendItems([.account, .preferences], toSection: .main)
+ snapshot.appendItems([.preferences], toSection: .main)
}
snapshot.appendSections([.version, .problemReport])
@@ -154,27 +139,4 @@ final class SettingsDataSource: UITableViewDiffableDataSource<
apply(snapshot)
}
-
- private func didUpdateDeviceState(_ deviceState: DeviceState) {
- let newAccountData = deviceState.accountData
- let oldAccountData = storedAccountData
-
- storedAccountData = newAccountData
-
- // Refresh individual row if expiry changed.
- if let newAccountData = newAccountData, let oldAccountData = oldAccountData,
- oldAccountData.number == newAccountData.number,
- oldAccountData.expiry != newAccountData.expiry
- {
- if let indexPath = indexPath(for: .account),
- let cell = tableView?.cellForRow(at: indexPath)
- {
- settingsCellFactory.configureCell(cell, item: .account, indexPath: indexPath)
- }
-
- return
- }
-
- updateDataSnapshot()
- }
}
diff --git a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
index 158c91b7b0..d9fd4771c1 100644
--- a/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
+++ b/ios/MullvadVPN/View controllers/Settings/SettingsViewController.swift
@@ -80,8 +80,6 @@ class SettingsViewController: UITableViewController, SettingsDataSourceDelegate
extension SettingsDataSource.Item {
var navigationRoute: SettingsNavigationRoute? {
switch self {
- case .account:
- return .account
case .preferences:
return .preferences
case .version:
diff --git a/ios/convert-assets.rb b/ios/convert-assets.rb
index 669b23fc42..21feaf7c99 100755
--- a/ios/convert-assets.rb
+++ b/ios/convert-assets.rb
@@ -6,7 +6,7 @@ SCRIPT_DIR = File.expand_path(File.dirname(__FILE__))
ROOT_DIR = File.dirname(SCRIPT_DIR)
# assets catalogue root
-XCASSETS_DIR = File.join(SCRIPT_DIR, "MullvadVPN/Assets.xcassets")
+XCASSETS_DIR = File.join(SCRIPT_DIR, "MullvadVPN/Supporting Files/Assets.xcassets")
# graphical assets sources
APPICON_PATH = File.join(ROOT_DIR, "graphics/icon-square.svg")
@@ -19,6 +19,7 @@ XCASSETS_APPICON_SIZE = 1024
# graphical assets to import
GRAPHICAL_ASSETS = [
+ "icon-account.svg",
"icon-arrow.svg",
"icon-back.svg",
"icon-chevron-down.svg",
@@ -59,9 +60,9 @@ ADDITIONAL_ASSETS = [
"IconBackTransitionMask.svg"
]
-# SVG conversion tool environment variables.
+# SVG conversion tool environment variables.
SVG_CONVERT_ENVIRONMENT_VARIABLES = {
- # Fix PDF "CreationDate" for reproducible output
+ # Fix PDF "CreationDate" for reproducible output
"SOURCE_DATE_EPOCH" => "1596022781"
}