diff options
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 Binary files differnew file mode 100644 index 0000000000..1d57a67c83 --- /dev/null +++ b/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconAccount.imageset/IconAccount.pdf diff --git a/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconSettings.imageset/IconSettings.pdf b/ios/MullvadVPN/Supporting Files/Assets.xcassets/IconSettings.imageset/IconSettings.pdf Binary files differindex 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 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" } |
