diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-02-26 17:37:27 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-02-26 17:37:27 +0100 |
| commit | 9a24ead9531e4989b57f537d91ca4cc256fbcb5f (patch) | |
| tree | 74ec5b7617aaf0eb0ff65ceb12be8c19ef188d4d | |
| parent | 4f712092e7de0613748ee3d3e39c07537d503b64 (diff) | |
| parent | e8b3e4e487933516f68714bf29dcf72ed5cca8ac (diff) | |
| download | mullvadvpn-9a24ead9531e4989b57f537d91ca4cc256fbcb5f.tar.xz mullvadvpn-9a24ead9531e4989b57f537d91ca4cc256fbcb5f.zip | |
Merge branch 'add-consent-screen'
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 16 | ||||
| -rw-r--r-- | ios/MullvadVPN/Account.swift | 11 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountViewController.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/AppButton.swift | 271 | ||||
| -rw-r--r-- | ios/MullvadVPN/AppDelegate.swift | 38 | ||||
| -rw-r--r-- | ios/MullvadVPN/Assets.xcassets/NewLogoIcon.imageset/Contents.json | 15 | ||||
| -rw-r--r-- | ios/MullvadVPN/Assets.xcassets/NewLogoIcon.imageset/NewLogoIcon.pdf | bin | 0 -> 2604 bytes | |||
| -rw-r--r-- | ios/MullvadVPN/Base.lproj/Main.storyboard | 269 | ||||
| -rw-r--r-- | ios/MullvadVPN/ConnectViewController.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/ConsentViewController.swift | 49 | ||||
| -rw-r--r-- | ios/MullvadVPN/CustomButton.swift | 68 | ||||
| -rw-r--r-- | ios/MullvadVPN/Info.plist | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN/LoginViewController.swift | 14 | ||||
| -rw-r--r-- | ios/MullvadVPN/RootContainerViewController.swift | 111 | ||||
| -rw-r--r-- | ios/MullvadVPN/UIColor+Palette.swift | 6 | ||||
| -rw-r--r-- | ios/MullvadVPN/ViewControllerIdentifier.swift | 1 | ||||
| -rw-r--r-- | ios/MullvadVPN/WebLinks.swift | 20 | ||||
| -rw-r--r-- | ios/MullvadVPN/WireguardKeysViewController.swift | 4 |
18 files changed, 662 insertions, 241 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 4d97e11152..46c230a192 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 5860F1EB23AA4CF300CEA666 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5860F1EA23AA4CF300CEA666 /* Logging.swift */; }; 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */; }; 5867A51C2248F26A005513C0 /* SegueIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867A51B2248F26A005513C0 /* SegueIdentifier.swift */; }; + 5868585524054096000B8131 /* AppButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5868585424054096000B8131 /* AppButton.swift */; }; 586AA296234B696B00502875 /* WireguardAssociatedAddresses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B8743122B25A7600015324 /* WireguardAssociatedAddresses.swift */; }; 586BD68322B7BBD800BB7F9F /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 586BD68222B7BBD800BB7F9F /* NetworkExtension.framework */; }; 586BD68422B7BBE400BB7F9F /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 586BD68222B7BBD800BB7F9F /* NetworkExtension.framework */; }; @@ -52,7 +53,6 @@ 587AD7C82342237300E93A53 /* TunnelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5835B7CB233B76CB0096D79F /* TunnelManager.swift */; }; 587AD7CA2342283900E93A53 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587AD7C92342283900E93A53 /* Account.swift */; }; 587B08E0229433EB000E6F17 /* LoginState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B08DF229433EB000E6F17 /* LoginState.swift */; }; - 587B08E2229460C1000E6F17 /* WebLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B08E1229460C1000E6F17 /* WebLinks.swift */; }; 587CBFE322807F530028DED3 /* UIColor+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587CBFE222807F530028DED3 /* UIColor+Helpers.swift */; }; 5888AD7F2279B6BF0051EB06 /* RelayStatusIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5888AD7E2279B6BF0051EB06 /* RelayStatusIndicatorView.swift */; }; 5888AD83227B11080051EB06 /* SelectLocationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5888AD82227B11080051EB06 /* SelectLocationCell.swift */; }; @@ -60,13 +60,13 @@ 5888AD89227B18C40051EB06 /* RelayList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5888AD88227B18C40051EB06 /* RelayList.swift */; }; 588AE72F2362001F009F9F2E /* MutuallyExclusive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588AE72E2362001F009F9F2E /* MutuallyExclusive.swift */; }; 588AE730236200E2009F9F2E /* MutuallyExclusive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588AE72E2362001F009F9F2E /* MutuallyExclusive.swift */; }; - 5894FC492296A8090017471D /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5894FC482296A8090017471D /* CustomButton.swift */; }; 589AB4F7227B64450039131E /* BasicTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 589AB4F6227B64450039131E /* BasicTableViewCell.swift */; }; 58A1AA8723F43901009F7EA6 /* GeoLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8623F43901009F7EA6 /* GeoLocation.swift */; }; 58A1AA8823F43901009F7EA6 /* GeoLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8623F43901009F7EA6 /* GeoLocation.swift */; }; 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */; }; 58A8BE81239FBE62006B74AC /* IPEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58561C98239A5D1500BD6B5E /* IPEndpoint.swift */; }; 58A8BE8323A0F362006B74AC /* UIAlertController+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A8BE8223A0F362006B74AC /* UIAlertController+Error.swift */; }; + 58A99ED3240014A0006599E9 /* ConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A99ED2240014A0006599E9 /* ConsentViewController.swift */; }; 58ADDB3C227B1BD200FAFEA7 /* JsonRpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ADDB3B227B1BD200FAFEA7 /* JsonRpc.swift */; }; 58ADDB3E227B1CD900FAFEA7 /* MullvadAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ADDB3D227B1CD900FAFEA7 /* MullvadAPI.swift */; }; 58AEEF652344A36000C9BBD5 /* KeychainError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AEEF642344A36000C9BBD5 /* KeychainError.swift */; }; @@ -185,6 +185,7 @@ 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslucentButtonBlurView.swift; sourceTree = "<group>"; }; 5866F39B2243B82D00168AE5 /* MullvadVPN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MullvadVPN.entitlements; sourceTree = "<group>"; }; 5867A51B2248F26A005513C0 /* SegueIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegueIdentifier.swift; sourceTree = "<group>"; }; + 5868585424054096000B8131 /* AppButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppButton.swift; sourceTree = "<group>"; }; 586BD68222B7BBD800BB7F9F /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 58723E7422A54C63009837F5 /* libwg-go.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5873884C239E6D7E00E96C4E /* EmbeddedViewContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedViewContainerView.swift; sourceTree = "<group>"; }; @@ -197,7 +198,6 @@ 587AD7C523421D7000E93A53 /* TunnelConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelConfiguration.swift; sourceTree = "<group>"; }; 587AD7C92342283900E93A53 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; }; 587B08DF229433EB000E6F17 /* LoginState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginState.swift; sourceTree = "<group>"; }; - 587B08E1229460C1000E6F17 /* WebLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebLinks.swift; sourceTree = "<group>"; }; 587CBFE222807F530028DED3 /* UIColor+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Helpers.swift"; sourceTree = "<group>"; }; 5888AD7E2279B6BF0051EB06 /* RelayStatusIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayStatusIndicatorView.swift; sourceTree = "<group>"; }; 5888AD82227B11080051EB06 /* SelectLocationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationCell.swift; sourceTree = "<group>"; }; @@ -205,11 +205,11 @@ 5888AD88227B18C40051EB06 /* RelayList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayList.swift; sourceTree = "<group>"; }; 588AE72E2362001F009F9F2E /* MutuallyExclusive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutuallyExclusive.swift; sourceTree = "<group>"; }; 5894E725236B2801008A2793 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; - 5894FC482296A8090017471D /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = "<group>"; }; 589AB4F6227B64450039131E /* BasicTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicTableViewCell.swift; sourceTree = "<group>"; }; 58A1AA8623F43901009F7EA6 /* GeoLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoLocation.swift; sourceTree = "<group>"; }; 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPanelView.swift; sourceTree = "<group>"; }; 58A8BE8223A0F362006B74AC /* UIAlertController+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Error.swift"; sourceTree = "<group>"; }; + 58A99ED2240014A0006599E9 /* ConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentViewController.swift; sourceTree = "<group>"; }; 58ADDB3B227B1BD200FAFEA7 /* JsonRpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonRpc.swift; sourceTree = "<group>"; }; 58ADDB3D227B1CD900FAFEA7 /* MullvadAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadAPI.swift; sourceTree = "<group>"; }; 58AEEF642344A36000C9BBD5 /* KeychainError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainError.swift; sourceTree = "<group>"; }; @@ -343,6 +343,7 @@ 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */, 58CCA01D2242787B004F3011 /* AccountTextField.swift */, 58CCA01722426713004F3011 /* AccountViewController.swift */, + 5868585424054096000B8131 /* AppButton.swift */, 58CE5E63224146200008646E /* AppDelegate.swift */, 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */, 58CE5E6A224146210008646E /* Assets.xcassets */, @@ -351,7 +352,7 @@ 58EC4E6B23915325003F5C5B /* Bundle+MullvadVersion.swift */, 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */, 58CCA00F224249A1004F3011 /* ConnectViewController.swift */, - 5894FC482296A8090017471D /* CustomButton.swift */, + 58A99ED2240014A0006599E9 /* ConsentViewController.swift */, 582BB1B0229569620055B6EF /* CustomNavigationBar.swift */, 58C6B35D22BBBFE3003C19AD /* Data+HexCoding.swift */, 5873884C239E6D7E00E96C4E /* EmbeddedViewContainerView.swift */, @@ -404,7 +405,6 @@ 587CBFE222807F530028DED3 /* UIColor+Helpers.swift */, 58CCA0152242560B004F3011 /* UIColor+Palette.swift */, 581CBCE52296B97300727D7F /* ViewControllerIdentifier.swift */, - 587B08E1229460C1000E6F17 /* WebLinks.swift */, 58B8743122B25A7600015324 /* WireguardAssociatedAddresses.swift */, 5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */, 58C6B35322BB87C4003C19AD /* WireguardPrivateKey.swift */, @@ -716,15 +716,16 @@ 58C6B35422BB87C4003C19AD /* WireguardPrivateKey.swift in Sources */, 5888AD87227B17950051EB06 /* SelectLocationController.swift in Sources */, 58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */, + 58A99ED3240014A0006599E9 /* ConsentViewController.swift in Sources */, 5845F838236C466400B2D93C /* TunnelControlViewController.swift in Sources */, 58CCA0162242560B004F3011 /* UIColor+Palette.swift in Sources */, 58AEEF6B2344A46200C9BBD5 /* TunnelConfigurationManager.swift in Sources */, 587CBFE322807F530028DED3 /* UIColor+Helpers.swift in Sources */, 581CBCEC2298041B00727D7F /* SettingsAppVersionCell.swift in Sources */, 5845F83A236C6A7200B2D93C /* AutoDisposableSink.swift in Sources */, - 587B08E2229460C1000E6F17 /* WebLinks.swift in Sources */, 5840250422B11AB700E4CFEC /* MullvadEndpoint.swift in Sources */, 58CCA01822426713004F3011 /* AccountViewController.swift in Sources */, + 5868585524054096000B8131 /* AppButton.swift in Sources */, 5845F842236CBACD00B2D93C /* PacketTunnelIpc.swift in Sources */, 58781CC922AE7CA8009B9D8E /* RelayConstraints.swift in Sources */, 58ADDB3E227B1CD900FAFEA7 /* MullvadAPI.swift in Sources */, @@ -750,7 +751,6 @@ 589AB4F7227B64450039131E /* BasicTableViewCell.swift in Sources */, 5888AD7F2279B6BF0051EB06 /* RelayStatusIndicatorView.swift in Sources */, 5867A51C2248F26A005513C0 /* SegueIdentifier.swift in Sources */, - 5894FC492296A8090017471D /* CustomButton.swift in Sources */, 58CCA01E2242787B004F3011 /* AccountTextField.swift in Sources */, 587AD7CA2342283900E93A53 /* Account.swift in Sources */, 58A8BE8323A0F362006B74AC /* UIAlertController+Error.swift in Sources */, diff --git a/ios/MullvadVPN/Account.swift b/ios/MullvadVPN/Account.swift index bd82097477..7ee61f5a76 100644 --- a/ios/MullvadVPN/Account.swift +++ b/ios/MullvadVPN/Account.swift @@ -69,6 +69,7 @@ extension AccountError: LocalizedError { /// A enum holding the `UserDefaults` string keys private enum UserDefaultsKeys: String { + case isAgreedToTermsOfService = "isAgreedToTermsOfService" case accountToken = "accountToken" case accountExpiry = "accountExpiry" } @@ -79,6 +80,11 @@ class Account { static let shared = Account() private let apiClient = MullvadAPI() + /// Returns true if user agreed to terms of service, otherwise false + var isAgreedToTermsOfService: Bool { + return UserDefaults.standard.bool(forKey: UserDefaultsKeys.isAgreedToTermsOfService.rawValue) + } + /// Returns the currently used account token var token: String? { return UserDefaults.standard.string(forKey: UserDefaultsKeys.accountToken.rawValue) @@ -93,6 +99,11 @@ class Account { return token != nil } + /// Save the boolean flag in preferences indicating that the user agreed to terms of service. + func agreeToTermsOfService() { + UserDefaults.standard.set(true, forKey: UserDefaultsKeys.isAgreedToTermsOfService.rawValue) + } + /// Perform the login and save the account token along with expiry (if available) to the /// application preferences. func login(with accountToken: String) -> AnyPublisher<(), AccountError> { diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift index e081b88858..6456140cf0 100644 --- a/ios/MullvadVPN/AccountViewController.swift +++ b/ios/MullvadVPN/AccountViewController.swift @@ -37,10 +37,6 @@ class AccountViewController: UIViewController { // MARK: - Actions - @IBAction func doBuyCredits() { - UIApplication.shared.open(WebLinks.purchaseURL, options: [:]) - } - @IBAction func doLogout() { let message = NSLocalizedString("Logging out. Please wait...", comment: "A modal message displayed during log out") diff --git a/ios/MullvadVPN/AppButton.swift b/ios/MullvadVPN/AppButton.swift new file mode 100644 index 0000000000..b98cc9c2cc --- /dev/null +++ b/ios/MullvadVPN/AppButton.swift @@ -0,0 +1,271 @@ +// +// AppButton.swift +// MullvadVPN +// +// Created by pronebird on 23/05/2019. +// Copyright © 2019 Mullvad VPN AB. All rights reserved. +// + +import UIKit + +enum ButtonImageAlignment { + /// Align image at the left edge of the title label + case left + + /// Align image at the right edge of the title label + case right + + /// Align image at the leading edge of the title label + case leading + + /// Align image at the trailing edge of the title label + case trailing + + /// Align image at the leading edge of content area + case leadingFixed + + /// Align image at the trailing edge of the content area + case trailingFixed + + /// Align image at the left edge of the content area + case leftFixed + + /// Align image at the right edge of the content area + case rightFixed +} + +private extension UIControl.State { + var customButtonTitleColor: UIColor? { + switch self { + case .normal: + return UIColor.AppButton.normalTitleColor + case .disabled: + return UIColor.AppButton.disabledTitleColor + case .highlighted: + return UIColor.AppButton.highlightedTitleColor + default: + return nil + } + } +} + +/// A subclass that implements the button that visually look like URL links on the web +@IBDesignable class LinkButton: CustomButton { + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + override func setTitle(_ title: String?, for state: UIControl.State) { + if let title = title { + setAttributedTitle(makeAttributedTitle(title, for: state), for: state) + } else { + setAttributedTitle(nil, for: state) + } + } + + private func commonInit() { + imageAlignment = .trailing + + let states: [UIControl.State] = [.normal, .highlighted, .disabled] + states.forEach { (state) in + if let title = self.title(for: state) { + let attributedTitle = makeAttributedTitle(title, for: state) + self.setAttributedTitle(attributedTitle, for: state) + } + } + } + + private func makeAttributedTitle(_ title: String, for state: UIControl.State) -> NSAttributedString { + var attributes: [NSAttributedString.Key: Any] = [ + .underlineStyle: NSUnderlineStyle.single.rawValue + ] + + if let titleColor = state.customButtonTitleColor { + attributes[.foregroundColor] = titleColor + } + + return NSAttributedString(string: title, attributes: attributes) + } +} + +/// A subclass that implements action buttons used across the app +@IBDesignable class AppButton: CustomButton { + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonInit() + } + + private func commonInit() { + var contentInsets = contentEdgeInsets + + if contentInsets.top == 0 { + contentInsets.top = 10 + } + + if contentInsets.bottom == 0 { + contentInsets.bottom = 10 + } + + if contentInsets.right == 0 { + contentInsets.right = 10 + } + + if contentInsets.left == 0 { + contentInsets.left = 10 + } + + contentEdgeInsets = contentInsets + imageAlignment = .trailingFixed + + titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) + + let states: [UIControl.State] = [.normal, .highlighted, .disabled] + states.forEach { (state) in + if let titleColor = state.customButtonTitleColor { + setTitleColor(titleColor, for: state) + } + } + } + +} + +/// A custom `UIButton` subclass that implements additional layouts for the image +@IBDesignable class CustomButton: UIButton { + + var imageAlignment: ButtonImageAlignment = .leading { + didSet { + invalidateIntrinsicContentSize() + } + } + + var inlineImageSpacing: CGFloat = 4 { + didSet { + invalidateIntrinsicContentSize() + } + } + + override var intrinsicContentSize: CGSize { + var intrinsicSize = super.intrinsicContentSize + + // Add spacing between the image and title label in intrinsic size calculation + if let imageSize = currentImage?.size, imageSize.width > 0 { + intrinsicSize.width += inlineImageSpacing + } + + return intrinsicSize + } + + var effectiveImageAlignment: ButtonImageAlignment { + switch (imageAlignment, effectiveUserInterfaceLayoutDirection) { + case (.left, _), + (.leading, .leftToRight), + (.trailing, .rightToLeft): + return .left + + case (.right, _), + (.trailing, .leftToRight), + (.leading, .rightToLeft): + return .right + + case (.leftFixed, _), + (.leadingFixed, .leftToRight), + (.trailingFixed, .rightToLeft): + return .leftFixed + + case (.rightFixed, _), + (.trailingFixed, .leftToRight), + (.leadingFixed, .rightToLeft): + return .rightFixed + + default: + fatalError() + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + private func commonInit() { + // Align the text color with the tint color which is applied to the image view + if let imageTintColor = UIControl.State.normal.customButtonTitleColor { + tintColor = imageTintColor + } + } + + private func computeLayout(forContentRect contentRect: CGRect) -> (CGRect, CGRect) { + var imageRect = super.imageRect(forContentRect: contentRect) + var titleRect = super.titleRect(forContentRect: contentRect) + + switch (effectiveContentHorizontalAlignment, effectiveImageAlignment) { + case (.left, .left): + imageRect.origin.x = contentRect.minX + titleRect.origin.x = imageRect.width > 0 + ? imageRect.maxX + inlineImageSpacing + : contentRect.minX + + case (.left, .right): + titleRect.origin.x = contentRect.minX + imageRect.origin.x = titleRect.maxX + inlineImageSpacing + + case (.left, .leftFixed): + imageRect.origin.x = contentRect.minX + titleRect.origin.x = imageRect.width > 0 + ? imageRect.maxX + inlineImageSpacing + : contentRect.minX + + case (.left, .rightFixed): + imageRect.origin.x = contentRect.maxX - imageRect.width + titleRect.origin.x = contentRect.minX + + case (.center, .leftFixed): + imageRect.origin.x = contentRect.minX + titleRect.origin.x = contentRect.midX - titleRect.width * 0.5 + + case (.center, .rightFixed): + imageRect.origin.x = contentRect.maxX - imageRect.width + titleRect.origin.x = contentRect.midX - titleRect.width * 0.5 + + case (.center, .left): + titleRect.origin.x = contentRect.midX - titleRect.width * 0.5 + imageRect.origin.x = titleRect.minX - inlineImageSpacing - imageRect.width + + case (.center, .right): + titleRect.origin.x = contentRect.midX - titleRect.width * 0.5 + imageRect.origin.x = titleRect.maxX + inlineImageSpacing + + default: + fatalError() + } + + return (titleRect, imageRect) + } + + override func imageRect(forContentRect contentRect: CGRect) -> CGRect { + return computeLayout(forContentRect: contentRect).1 + } + + override func titleRect(forContentRect contentRect: CGRect) -> CGRect { + return computeLayout(forContentRect: contentRect).0 + } + +} diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 84e0fd7f81..663911f877 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -39,24 +39,44 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let rootViewController = self.mainStoryboard.instantiateViewController(identifier: ViewControllerIdentifier.root.rawValue) as! RootContainerViewController - let loginViewController = self.mainStoryboard.instantiateViewController(withIdentifier: ViewControllerIdentifier.login.rawValue) + if Account.shared.isAgreedToTermsOfService { + self.showMainController(in: rootViewController, animated: false) + } else { + self.showTermsOfService(in: rootViewController) { + Account.shared.agreeToTermsOfService() - var viewControllers = [loginViewController] - - if accountToken != nil { - let mainViewController = self.mainStoryboard.instantiateViewController(withIdentifier: ViewControllerIdentifier.main.rawValue) - - viewControllers.append(mainViewController) + self.showMainController(in: rootViewController, animated: true) + } } - rootViewController.setViewControllers(viewControllers, animated: false) - self.window?.rootViewController = rootViewController }) return true } + private func showTermsOfService(in rootViewController: RootContainerViewController, completionHandler: @escaping () -> Void) { + let consentViewController = self.mainStoryboard.instantiateViewController(withIdentifier: ViewControllerIdentifier.consent.rawValue) as! ConsentViewController + + consentViewController.completionHandler = completionHandler + + rootViewController.setViewControllers([consentViewController], animated: false) + } + + private func showMainController(in rootViewController: RootContainerViewController, animated: Bool) { + let loginViewController = self.mainStoryboard.instantiateViewController(withIdentifier: ViewControllerIdentifier.login.rawValue) + + var viewControllers = [loginViewController] + + if Account.shared.isLoggedIn { + let mainViewController = self.mainStoryboard.instantiateViewController(withIdentifier: ViewControllerIdentifier.main.rawValue) + + viewControllers.append(mainViewController) + } + + rootViewController.setViewControllers(viewControllers, animated: animated) + } + func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. diff --git a/ios/MullvadVPN/Assets.xcassets/NewLogoIcon.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/NewLogoIcon.imageset/Contents.json new file mode 100644 index 0000000000..9e9f4e465b --- /dev/null +++ b/ios/MullvadVPN/Assets.xcassets/NewLogoIcon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "NewLogoIcon.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +}
\ No newline at end of file diff --git a/ios/MullvadVPN/Assets.xcassets/NewLogoIcon.imageset/NewLogoIcon.pdf b/ios/MullvadVPN/Assets.xcassets/NewLogoIcon.imageset/NewLogoIcon.pdf Binary files differnew file mode 100644 index 0000000000..a3200ee169 --- /dev/null +++ b/ios/MullvadVPN/Assets.xcassets/NewLogoIcon.imageset/NewLogoIcon.pdf diff --git a/ios/MullvadVPN/Base.lproj/Main.storyboard b/ios/MullvadVPN/Base.lproj/Main.storyboard index 45acb84b43..898f50bdff 100644 --- a/ios/MullvadVPN/Base.lproj/Main.storyboard +++ b/ios/MullvadVPN/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ZwP-1v-DUg"> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ZwP-1v-DUg"> <device id="retina4_7" orientation="portrait" appearance="light"/> <dependencies> - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/> <capability name="Named colors" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> @@ -21,7 +21,7 @@ </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="2sf-Y1-Ntj" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> </objects> - <point key="canvasLocation" x="-1274" y="27"/> + <point key="canvasLocation" x="-1962" y="27"/> </scene> <!--Root Container View Controller--> <scene sceneID="euw-TF-Dd7"> @@ -40,7 +40,7 @@ <rect key="frame" x="0.0" y="0.0" width="375" height="93"/> <subviews> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LogoIcon" translatesAutoresizingMaskIntoConstraints="NO" id="cKg-hE-JsS"> - <rect key="frame" x="12" y="31.5" width="49.000000000000014" height="50"/> + <rect key="frame" x="12" y="31.5" width="49" height="50"/> </imageView> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uXv-Tf-PET"> <rect key="frame" x="335" y="44.5" width="24" height="24"/> @@ -182,8 +182,8 @@ <constraint firstItem="V3j-Lb-fSQ" firstAttribute="leading" secondItem="0ZY-Kh-JiM" secondAttribute="leading" id="alr-G1-L4w"/> </constraints> </view> - <view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ire-2z-eJu" userLabel="Footer"> - <rect key="frame" x="0.0" y="554.5" width="375" height="112.5"/> + <view hidden="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ire-2z-eJu" userLabel="Footer"> + <rect key="frame" x="0.0" y="576.5" width="375" height="90.5"/> <subviews> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Don't have an account number?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QcG-Tf-YdQ"> <rect key="frame" x="24" y="16" width="327" height="20.5"/> @@ -191,10 +191,8 @@ <color key="textColor" white="1" alpha="0.60327482876712324" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> </label> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="osm-vd-aTb"> - <rect key="frame" x="24" y="44.5" width="327" height="44"/> - <fontDescription key="fontDescription" type="system" pointSize="20"/> - <inset key="contentEdgeInsets" minX="0.0" minY="10" maxX="0.0" maxY="10"/> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="osm-vd-aTb" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="24" y="44.5" width="327" height="22"/> <state key="normal" title="Create account" backgroundImage="DefaultButton"> <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> </state> @@ -369,20 +367,20 @@ <color key="separatorColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <prototypes> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Account" id="ghE-jC-RWf" customClass="SettingsAccountCell" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="55.5" width="375" height="43"/> + <rect key="frame" x="0.0" y="55.5" width="375" height="43.5"/> <autoresizingMask key="autoresizingMask"/> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ghE-jC-RWf" id="sTl-gI-g2a"> - <rect key="frame" x="0.0" y="0.0" width="348" height="43"/> + <rect key="frame" x="0.0" y="0.0" width="348" height="43.5"/> <autoresizingMask key="autoresizingMask"/> <subviews> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Account" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Lve-Kd-qTr"> - <rect key="frame" x="16" y="11" width="63.5" height="21"/> + <rect key="frame" x="16" y="11" width="63.5" height="21.5"/> <fontDescription key="fontDescription" type="system" pointSize="17"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> </label> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="A YEAR LEFT" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QeD-EQ-Ruo"> - <rect key="frame" x="259" y="11" width="81" height="21"/> + <rect key="frame" x="259" y="11" width="81" height="21.5"/> <fontDescription key="fontDescription" type="system" weight="medium" pointSize="13"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> @@ -407,20 +405,20 @@ </connections> </tableViewCell> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="AppVersion" id="pbd-iC-Emm" customClass="SettingsAppVersionCell" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="98.5" width="375" height="43"/> + <rect key="frame" x="0.0" y="99" width="375" height="43.5"/> <autoresizingMask key="autoresizingMask"/> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="pbd-iC-Emm" id="lYp-Z8-1sN"> - <rect key="frame" x="0.0" y="0.0" width="375" height="43"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/> <autoresizingMask key="autoresizingMask"/> <subviews> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="App version" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pYC-Zb-8N9"> - <rect key="frame" x="16" y="11" width="91" height="21"/> + <rect key="frame" x="16" y="11" width="91" height="21.5"/> <fontDescription key="fontDescription" type="system" pointSize="17"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> </label> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="2018.3" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sOr-vj-cg7"> - <rect key="frame" x="316.5" y="11" width="42.5" height="21"/> + <rect key="frame" x="316.5" y="11" width="42.5" height="21.5"/> <fontDescription key="fontDescription" type="system" weight="medium" pointSize="13"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> @@ -442,14 +440,14 @@ </connections> </tableViewCell> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="BasicDisclosure" id="Ahs-gu-nTM" customClass="SettingsBasicCell" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="141.5" width="375" height="43"/> + <rect key="frame" x="0.0" y="142.5" width="375" height="43.5"/> <autoresizingMask key="autoresizingMask"/> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Ahs-gu-nTM" id="Drq-vk-8F2"> - <rect key="frame" x="0.0" y="0.0" width="348" height="43"/> + <rect key="frame" x="0.0" y="0.0" width="348" height="43.5"/> <autoresizingMask key="autoresizingMask"/> <subviews> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Amw-A3-ePS"> - <rect key="frame" x="16" y="11" width="324" height="21"/> + <rect key="frame" x="16" y="11" width="324" height="21.5"/> <fontDescription key="fontDescription" type="system" pointSize="17"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> @@ -507,10 +505,10 @@ <rect key="frame" x="0.0" y="0.0" width="375" height="647"/> <subviews> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5VO-oQ-4jM" userLabel="Container"> - <rect key="frame" x="0.0" y="0.0" width="375" height="361"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="295"/> <subviews> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Lx5-tV-hNL" userLabel="Content"> - <rect key="frame" x="24" y="24" width="327" height="313"/> + <rect key="frame" x="24" y="24" width="327" height="247"/> <subviews> <view contentMode="scaleToFill" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="xch-VD-kOQ" userLabel="Account number"> <rect key="frame" x="0.0" y="0.0" width="327" height="45.5"/> @@ -594,44 +592,35 @@ </constraints> </view> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ydQ-IP-KZb" userLabel="Buttons"> - <rect key="frame" x="0.0" y="139" width="327" height="174"/> + <rect key="frame" x="0.0" y="139" width="327" height="108"/> <subviews> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="zF0-5W-t7M"> - <rect key="frame" x="0.0" y="0.0" width="327" height="174"/> + <rect key="frame" x="0.0" y="0.0" width="327" height="108"/> <subviews> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OCa-Jz-b7W" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OCa-Jz-b7W" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> <rect key="frame" x="0.0" y="0.0" width="327" height="42"/> <constraints> <constraint firstAttribute="height" constant="42" placeholder="YES" id="IpC-KC-l52"/> </constraints> + <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/> <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <state key="normal" title="Regenerate key" backgroundImage="SuccessButton"/> <connections> <action selector="handleRegenerateKey:" destination="vAK-MJ-h3c" eventType="touchUpInside" id="s39-bg-vkG"/> </connections> </button> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qEF-8w-MdR" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qEF-8w-MdR" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> <rect key="frame" x="0.0" y="66" width="327" height="42"/> <constraints> <constraint firstAttribute="height" constant="42" placeholder="YES" id="299-Lu-yIB"/> </constraints> + <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/> <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> - <state key="normal" title="Verify key" backgroundImage="SuccessButton"/> + <state key="normal" title="Verify key" backgroundImage="DefaultButton"/> <connections> <action selector="handleVerifyKey:" destination="vAK-MJ-h3c" eventType="touchUpInside" id="wGf-5k-Zw2"/> </connections> </button> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4U5-oz-pbv" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="132" width="327" height="42"/> - <constraints> - <constraint firstAttribute="height" constant="42" placeholder="YES" id="eTr-Qz-83C"/> - </constraints> - <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> - <state key="normal" title="Manage keys" image="IconExtlink" backgroundImage="SuccessButton"/> - <connections> - <action selector="handleManageKeys:" destination="vAK-MJ-h3c" eventType="touchUpInside" id="DDg-kZ-9k6"/> - </connections> - </button> </subviews> </stackView> </subviews> @@ -747,10 +736,10 @@ <rect key="frame" x="0.0" y="0.0" width="375" height="647"/> <subviews> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rkG-Xa-pEO" userLabel="Container"> - <rect key="frame" x="0.0" y="0.0" width="375" height="295.5"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="229.5"/> <subviews> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="nkx-Eb-7le" userLabel="Content"> - <rect key="frame" x="24" y="24" width="327" height="247.5"/> + <rect key="frame" x="24" y="24" width="327" height="181.5"/> <subviews> <view contentMode="scaleToFill" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="HzF-8Z-UBs" userLabel="Account number"> <rect key="frame" x="0.0" y="0.0" width="327" height="46"/> @@ -791,7 +780,7 @@ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="NMg-f0-BTW"> <rect key="frame" x="0.0" y="0.0" width="327" height="45.5"/> <subviews> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Paid until" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nrG-9Q-lWI"> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Active until" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nrG-9Q-lWI"> <rect key="frame" x="0.0" y="0.0" width="327" height="17"/> <fontDescription key="fontDescription" type="system" pointSize="14"/> <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> @@ -813,56 +802,29 @@ <constraint firstItem="NMg-f0-BTW" firstAttribute="leading" secondItem="459-0n-9V2" secondAttribute="leading" id="vqI-Vt-8V6"/> </constraints> </view> - <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Acd-vw-Pu7" userLabel="Buttons"> - <rect key="frame" x="0.0" y="139.5" width="327" height="108"/> - <subviews> - <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="wNk-FP-mVD"> - <rect key="frame" x="0.0" y="0.0" width="327" height="108"/> - <subviews> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2Ia-Dz-pE6" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="0.0" width="327" height="42"/> - <constraints> - <constraint firstAttribute="height" constant="42" placeholder="YES" id="9Ss-ab-obH"/> - </constraints> - <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> - <state key="normal" title="Buy more credit" image="IconExtlink" backgroundImage="SuccessButton"/> - <connections> - <action selector="doBuyCredits" destination="ruh-Q2-P39" eventType="touchUpInside" id="BAj-cb-ENq"/> - </connections> - </button> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QHr-Lz-v6t" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="66" width="327" height="42"/> - <accessibility key="accessibilityConfiguration" identifier="LogoutButton"/> - <constraints> - <constraint firstAttribute="height" constant="42" placeholder="YES" id="VYx-GQ-CIz"/> - </constraints> - <state key="normal" title="Log out" backgroundImage="DangerButton"> - <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> - </state> - <connections> - <action selector="doLogout" destination="ruh-Q2-P39" eventType="touchUpInside" id="CVm-Qx-5Et"/> - </connections> - </button> - </subviews> - </stackView> - </subviews> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QHr-Lz-v6t" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="0.0" y="139.5" width="327" height="42"/> + <accessibility key="accessibilityConfiguration" identifier="LogoutButton"/> <constraints> - <constraint firstAttribute="bottom" secondItem="wNk-FP-mVD" secondAttribute="bottom" id="5v8-RH-7Dt"/> - <constraint firstItem="wNk-FP-mVD" firstAttribute="top" secondItem="Acd-vw-Pu7" secondAttribute="top" id="F8Z-m4-Mdt"/> - <constraint firstAttribute="trailing" secondItem="wNk-FP-mVD" secondAttribute="trailing" id="alb-J1-Saf"/> - <constraint firstItem="wNk-FP-mVD" firstAttribute="leading" secondItem="Acd-vw-Pu7" secondAttribute="leading" id="yMu-Ta-4ad"/> + <constraint firstAttribute="height" constant="42" placeholder="YES" id="VYx-GQ-CIz"/> </constraints> - </view> + <state key="normal" title="Log out" backgroundImage="DangerButton"> + <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + </state> + <connections> + <action selector="doLogout" destination="ruh-Q2-P39" eventType="touchUpInside" id="CVm-Qx-5Et"/> + </connections> + </button> </subviews> <constraints> - <constraint firstAttribute="trailing" secondItem="Acd-vw-Pu7" secondAttribute="trailing" id="AzF-Vm-XaC"/> + <constraint firstItem="QHr-Lz-v6t" firstAttribute="top" secondItem="459-0n-9V2" secondAttribute="bottom" constant="24" id="6IV-09-erh"/> + <constraint firstItem="QHr-Lz-v6t" firstAttribute="leading" secondItem="nkx-Eb-7le" secondAttribute="leading" id="EEA-bt-bSx"/> <constraint firstItem="459-0n-9V2" firstAttribute="leading" secondItem="nkx-Eb-7le" secondAttribute="leading" id="G86-ck-dqe"/> <constraint firstAttribute="trailing" secondItem="459-0n-9V2" secondAttribute="trailing" id="HUb-T5-Wkk"/> - <constraint firstAttribute="bottom" secondItem="Acd-vw-Pu7" secondAttribute="bottom" id="JwY-fr-wzM"/> - <constraint firstItem="Acd-vw-Pu7" firstAttribute="leading" secondItem="nkx-Eb-7le" secondAttribute="leading" id="Lf9-6X-tCB"/> - <constraint firstItem="Acd-vw-Pu7" firstAttribute="top" secondItem="459-0n-9V2" secondAttribute="bottom" constant="24" id="ShJ-z8-Scs"/> <constraint firstItem="459-0n-9V2" firstAttribute="top" secondItem="HzF-8Z-UBs" secondAttribute="bottom" constant="24" id="Ttn-aK-Cj0"/> <constraint firstItem="HzF-8Z-UBs" firstAttribute="leading" secondItem="nkx-Eb-7le" secondAttribute="leading" id="bCL-Z9-nk4"/> + <constraint firstAttribute="trailing" secondItem="QHr-Lz-v6t" secondAttribute="trailing" id="eBz-Is-dHp"/> + <constraint firstAttribute="bottom" secondItem="QHr-Lz-v6t" secondAttribute="bottom" id="fRA-bC-3eO"/> <constraint firstAttribute="trailing" secondItem="HzF-8Z-UBs" secondAttribute="trailing" id="pVC-Ci-c98"/> <constraint firstItem="HzF-8Z-UBs" firstAttribute="top" secondItem="nkx-Eb-7le" secondAttribute="top" id="vsH-Ee-fch"/> </constraints> @@ -924,6 +886,130 @@ </objects> <point key="canvasLocation" x="670" y="-832"/> </scene> + <!--Consent View Controller--> + <scene sceneID="dxQ-uf-ugD"> + <objects> + <viewController storyboardIdentifier="Consent" id="kLI-jR-tKo" customClass="ConsentViewController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController"> + <view key="view" contentMode="scaleToFill" id="xQJ-oi-zn6"> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JYh-33-d0O"> + <rect key="frame" x="0.0" y="0.0" width="375" height="597"/> + <subviews> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="N9k-cQ-tlw" userLabel="Content view"> + <rect key="frame" x="0.0" y="0.0" width="375" height="558"/> + <subviews> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Wnl-L9-JqG" userLabel="Logo header"> + <rect key="frame" x="0.0" y="0.0" width="375" height="100"/> + <subviews> + <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="NewLogoIcon" translatesAutoresizingMaskIntoConstraints="NO" id="WSx-4V-zIk"> + <rect key="frame" x="137.5" y="0.0" width="100" height="100"/> + <constraints> + <constraint firstAttribute="width" secondItem="WSx-4V-zIk" secondAttribute="height" multiplier="1:1" id="ZtE-hc-rs8"/> + <constraint firstAttribute="width" constant="100" id="qGt-Am-MHR"/> + </constraints> + </imageView> + </subviews> + <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <constraints> + <constraint firstItem="WSx-4V-zIk" firstAttribute="centerX" secondItem="Wnl-L9-JqG" secondAttribute="centerX" id="30b-jz-Tpk"/> + <constraint firstAttribute="bottom" secondItem="WSx-4V-zIk" secondAttribute="bottom" id="3FY-d7-yKL"/> + <constraint firstItem="WSx-4V-zIk" firstAttribute="top" secondItem="Wnl-L9-JqG" secondAttribute="top" id="ekz-Kj-ng1"/> + </constraints> + </view> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Do you agree to remaining anonymous?" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KiF-h3-6a4"> + <rect key="frame" x="20" y="100" width="335" height="57.5"/> + <fontDescription key="fontDescription" type="boldSystem" pointSize="24"/> + <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dZ1-yd-jbD"> + <rect key="frame" x="20" y="181.5" width="335" height="334.5"/> + <string key="text">You have a right to privacy. That’s why we never store activity logs, don't ask for personal information, and encourage anonymous payments.
In some situations, as outlined in our privacy policy, we might process personal data that you choose to send, for example if you email us. +
We strongly believe in retaining as little data as possible because we want you to remain anonymous. +</string> + <fontDescription key="fontDescription" type="system" pointSize="20"/> + <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Cas-Tk-gcz" customClass="LinkButton" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="20" y="516" width="128" height="22"/> + <fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="18"/> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <state key="normal" title="Privacy Policy" image="IconExtlink"/> + <connections> + <action selector="handlePrivacyPolicyButton:" destination="kLI-jR-tKo" eventType="touchUpInside" id="Hm5-a0-LNm"/> + </connections> + </button> + </subviews> + <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <constraints> + <constraint firstAttribute="trailing" secondItem="KiF-h3-6a4" secondAttribute="trailing" constant="20" symbolic="YES" id="CK8-X1-Nkb"/> + <constraint firstItem="Wnl-L9-JqG" firstAttribute="leading" secondItem="N9k-cQ-tlw" secondAttribute="leading" id="FKE-Uf-7uJ"/> + <constraint firstAttribute="bottom" secondItem="Cas-Tk-gcz" secondAttribute="bottom" constant="20" symbolic="YES" id="Kda-4m-cai"/> + <constraint firstAttribute="trailing" secondItem="Wnl-L9-JqG" secondAttribute="trailing" id="MKQ-ko-Avx"/> + <constraint firstItem="Cas-Tk-gcz" firstAttribute="top" secondItem="dZ1-yd-jbD" secondAttribute="bottom" id="N88-cS-Ver"/> + <constraint firstItem="KiF-h3-6a4" firstAttribute="leading" secondItem="N9k-cQ-tlw" secondAttribute="leading" constant="20" symbolic="YES" id="NFy-JV-jZg"/> + <constraint firstAttribute="trailing" secondItem="dZ1-yd-jbD" secondAttribute="trailing" constant="20" symbolic="YES" id="Yyj-6q-s67"/> + <constraint firstItem="Wnl-L9-JqG" firstAttribute="bottom" secondItem="KiF-h3-6a4" secondAttribute="top" id="bjr-qL-pMb"/> + <constraint firstItem="dZ1-yd-jbD" firstAttribute="top" secondItem="KiF-h3-6a4" secondAttribute="bottom" constant="24" id="btD-0h-bhJ"/> + <constraint firstItem="dZ1-yd-jbD" firstAttribute="leading" secondItem="N9k-cQ-tlw" secondAttribute="leading" constant="20" symbolic="YES" id="eQC-X5-D2r"/> + <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Cas-Tk-gcz" secondAttribute="trailing" constant="20" symbolic="YES" id="jYh-Qy-r3y"/> + <constraint firstItem="Cas-Tk-gcz" firstAttribute="leading" secondItem="N9k-cQ-tlw" secondAttribute="leading" constant="20" symbolic="YES" id="uFa-a3-PG6"/> + <constraint firstItem="Wnl-L9-JqG" firstAttribute="top" secondItem="N9k-cQ-tlw" secondAttribute="top" id="zHJ-3T-ddt"/> + </constraints> + </view> + </subviews> + <constraints> + <constraint firstAttribute="bottom" secondItem="N9k-cQ-tlw" secondAttribute="bottom" id="GDa-a8-iDt"/> + <constraint firstAttribute="trailing" secondItem="N9k-cQ-tlw" secondAttribute="trailing" id="H5A-HQ-KaS"/> + <constraint firstItem="N9k-cQ-tlw" firstAttribute="leading" secondItem="JYh-33-d0O" secondAttribute="leading" id="XuX-5s-2by"/> + <constraint firstItem="N9k-cQ-tlw" firstAttribute="width" secondItem="JYh-33-d0O" secondAttribute="width" id="idS-Wd-3MB"/> + <constraint firstItem="N9k-cQ-tlw" firstAttribute="top" secondItem="JYh-33-d0O" secondAttribute="top" id="oMo-Do-1Dj"/> + </constraints> + </scrollView> + <view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="16P-Q0-ZO9" userLabel="Footer"> + <rect key="frame" x="0.0" y="597" width="375" height="70"/> + <subviews> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ttw-7B-1MM" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="16" y="24" width="343" height="22"/> + <state key="normal" title="Agree and continue" backgroundImage="DefaultButton"> + <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + </state> + <connections> + <action selector="handleAgreeAndContinueButton:" destination="kLI-jR-tKo" eventType="touchUpInside" id="wBI-Iz-pUM"/> + </connections> + </button> + </subviews> + <color key="backgroundColor" name="Secondary"/> + <constraints> + <constraint firstItem="ttw-7B-1MM" firstAttribute="top" secondItem="16P-Q0-ZO9" secondAttribute="topMargin" id="lkM-QB-3cu"/> + <constraint firstItem="ttw-7B-1MM" firstAttribute="trailing" secondItem="16P-Q0-ZO9" secondAttribute="trailingMargin" id="ntK-fj-CmJ"/> + <constraint firstAttribute="bottomMargin" secondItem="ttw-7B-1MM" secondAttribute="bottom" id="sFa-XA-rpD"/> + <constraint firstItem="ttw-7B-1MM" firstAttribute="leading" secondItem="16P-Q0-ZO9" secondAttribute="leadingMargin" id="zcA-ed-V7o"/> + </constraints> + <edgeInsets key="layoutMargins" top="24" left="16" bottom="24" right="16"/> + </view> + </subviews> + <color key="backgroundColor" name="Primary"/> + <constraints> + <constraint firstItem="16P-Q0-ZO9" firstAttribute="top" secondItem="JYh-33-d0O" secondAttribute="bottom" id="74M-Ho-5CP"/> + <constraint firstAttribute="bottom" secondItem="16P-Q0-ZO9" secondAttribute="bottom" id="IFF-x2-PtX"/> + <constraint firstAttribute="trailing" secondItem="JYh-33-d0O" secondAttribute="trailing" id="J2w-ev-9cV"/> + <constraint firstAttribute="trailing" secondItem="16P-Q0-ZO9" secondAttribute="trailing" id="cyQ-2k-LQX"/> + <constraint firstItem="JYh-33-d0O" firstAttribute="top" secondItem="xQJ-oi-zn6" secondAttribute="top" id="uYC-FA-dma"/> + <constraint firstItem="JYh-33-d0O" firstAttribute="leading" secondItem="xQJ-oi-zn6" secondAttribute="leading" id="ulR-7a-F1P"/> + <constraint firstItem="16P-Q0-ZO9" firstAttribute="leading" secondItem="xQJ-oi-zn6" secondAttribute="leading" id="wxq-ir-0JY"/> + </constraints> + <viewLayoutGuide key="safeArea" id="OP4-4u-dhX"/> + </view> + <navigationItem key="navigationItem" id="1zW-Vd-PiW"/> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="87X-aX-U9x" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="-1207" y="27"/> + </scene> <!--Navigation Controller--> <scene sceneID="oT4-Ap-qrZ"> <objects> @@ -1088,8 +1174,8 @@ <rect key="frame" x="0.0" y="0.0" width="261" height="52.5"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hVz-q0-Xpd" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="0.0" width="261" height="52"/> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hVz-q0-Xpd" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="0.0" y="0.0" width="261" height="52.5"/> <inset key="contentEdgeInsets" minX="0.0" minY="0.0" maxX="0.0" maxY="10"/> <state key="normal" title="Select location" backgroundImage="TranslucentNeutralButton"> <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> @@ -1109,7 +1195,7 @@ </view> <blurEffect style="light"/> </visualEffectView> - <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vxU-Mt-fMo" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vxU-Mt-fMo" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> <rect key="frame" x="0.0" y="68.5" width="261" height="52.5"/> <accessibility key="accessibilityConfiguration" identifier="ConnectButton"/> <state key="normal" title="Secure my connection" backgroundImage="SuccessButton"> @@ -1147,7 +1233,7 @@ <rect key="frame" x="0.0" y="0.0" width="261" height="52"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dbp-iY-d0K" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dbp-iY-d0K" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> <rect key="frame" x="0.0" y="0.0" width="261" height="52"/> <inset key="contentEdgeInsets" minX="0.0" minY="0.0" maxX="0.0" maxY="10"/> <state key="normal" title="Switch location" backgroundImage="TranslucentNeutralButton"> @@ -1173,7 +1259,7 @@ <rect key="frame" x="0.0" y="0.0" width="261" height="52"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="PDP-HS-RB2" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="PDP-HS-RB2" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> <rect key="frame" x="0.0" y="0.0" width="261" height="52"/> <state key="normal" title="Cancel" backgroundImage="TranslucentDangerButton"> <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> @@ -1220,7 +1306,7 @@ <rect key="frame" x="0.0" y="0.0" width="261" height="52"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZbQ-zA-ZS8" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZbQ-zA-ZS8" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> <rect key="frame" x="0.0" y="0.0" width="261" height="52"/> <inset key="contentEdgeInsets" minX="0.0" minY="0.0" maxX="0.0" maxY="10"/> <state key="normal" title="Switch location" backgroundImage="TranslucentNeutralButton"> @@ -1246,7 +1332,7 @@ <rect key="frame" x="0.0" y="0.0" width="261" height="52"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d5t-ia-qxF" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d5t-ia-qxF" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target"> <rect key="frame" x="0.0" y="0.0" width="261" height="52"/> <accessibility key="accessibilityConfiguration" identifier="DisconnectButton"/> <state key="normal" title="Disconnect" backgroundImage="TranslucentDangerButton"> @@ -1294,6 +1380,7 @@ <image name="IconSuccess" width="60" height="60"/> <image name="IconTick" width="24" height="24"/> <image name="LogoIcon" width="49" height="50"/> + <image name="NewLogoIcon" width="400" height="400"/> <image name="SuccessButton" width="9" height="9"/> <image name="TranslucentDangerButton" width="9" height="9"/> <image name="TranslucentNeutralButton" width="9" height="9"/> diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift index b16293d769..6a5c9527d0 100644 --- a/ios/MullvadVPN/ConnectViewController.swift +++ b/ios/MullvadVPN/ConnectViewController.swift @@ -36,6 +36,10 @@ class ConnectViewController: UIViewController, RootContainment, TunnelControlVie } } + var prefersHeaderBarHidden: Bool { + return false + } + private var tunnelState: TunnelState = .disconnected { didSet { setNeedsHeaderBarStyleAppearanceUpdate() diff --git a/ios/MullvadVPN/ConsentViewController.swift b/ios/MullvadVPN/ConsentViewController.swift new file mode 100644 index 0000000000..e9f471aa8c --- /dev/null +++ b/ios/MullvadVPN/ConsentViewController.swift @@ -0,0 +1,49 @@ +// +// ConsentViewController.swift +// MullvadVPN +// +// Created by pronebird on 21/02/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import SafariServices +import UIKit + +private let kPrivacyPolicyURL = URL(string: "https://mullvad.net/en/help/privacy-policy/?hide_nav")! + +class ConsentViewController: UIViewController, RootContainment, SFSafariViewControllerDelegate { + + var completionHandler: (() -> Void)? + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + var preferredHeaderBarStyle: HeaderBarStyle { + return .transparent + } + + var prefersHeaderBarHidden: Bool { + return true + } + + // MARK: - IBActions + + @IBAction func handlePrivacyPolicyButton(_ sender: Any) { + let safariController = SFSafariViewController(url: kPrivacyPolicyURL) + safariController.delegate = self + + present(safariController, animated: true) + } + + @IBAction func handleAgreeAndContinueButton(_ sender: Any) { + completionHandler?() + } + + // MARK: - SFSafariViewControllerDelegate + + func safariViewControllerDidFinish(_ controller: SFSafariViewController) { + controller.dismiss(animated: true) + } + +} diff --git a/ios/MullvadVPN/CustomButton.swift b/ios/MullvadVPN/CustomButton.swift deleted file mode 100644 index 1b5bcbece3..0000000000 --- a/ios/MullvadVPN/CustomButton.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// CustomButton.swift -// MullvadVPN -// -// Created by pronebird on 23/05/2019. -// Copyright © 2019 Mullvad VPN AB. All rights reserved. -// - -import UIKit - -@IBDesignable class CustomButton: UIButton { - - override init(frame: CGRect) { - super.init(frame: frame) - - commonInit() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - - commonInit() - } - - private func commonInit() { - var contentInsets = contentEdgeInsets - - if contentInsets.top == 0 { - contentInsets.top = 10 - } - - if contentInsets.bottom == 0 { - contentInsets.bottom = 10 - } - - if contentInsets.right == 0 { - contentInsets.right = 10 - } - - if contentInsets.left == 0 { - contentInsets.left = 10 - } - - contentEdgeInsets = contentInsets - titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) - - setTitleColor(UIColor.white, for: .normal) - setTitleColor(UIColor.lightGray, for: .highlighted) - setTitleColor(UIColor.lightGray, for: .disabled) - } - - override func imageRect(forContentRect contentRect: CGRect) -> CGRect { - var imageRect = super.imageRect(forContentRect: contentRect) - - imageRect.origin.x = contentRect.maxX - imageRect.size.width - - return imageRect - } - - override func titleRect(forContentRect contentRect: CGRect) -> CGRect { - var titleRect = super.titleRect(forContentRect: contentRect) - - titleRect.origin.x = contentRect.midX - titleRect.width * 0.5 - - return titleRect - } - -} diff --git a/ios/MullvadVPN/Info.plist b/ios/MullvadVPN/Info.plist index 5face849a6..b4fc95ec26 100644 --- a/ios/MullvadVPN/Info.plist +++ b/ios/MullvadVPN/Info.plist @@ -32,6 +32,8 @@ <array> <string>armv7</string> </array> + <key>UIStatusBarStyle</key> + <string>UIStatusBarStyleLightContent</string> <key>UISupportedInterfaceOrientations</key> <array> <string>UIInterfaceOrientationPortrait</string> diff --git a/ios/MullvadVPN/LoginViewController.swift b/ios/MullvadVPN/LoginViewController.swift index 7f88984a26..4383732191 100644 --- a/ios/MullvadVPN/LoginViewController.swift +++ b/ios/MullvadVPN/LoginViewController.swift @@ -42,6 +42,10 @@ class LoginViewController: UIViewController, UITextFieldDelegate, RootContainmen return .transparent } + var prefersHeaderBarHidden: Bool { + return false + } + override func viewDidLoad() { super.viewDidLoad() @@ -85,6 +89,12 @@ class LoginViewController: UIViewController, UITextFieldDelegate, RootContainmen object: accountTextField) } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + accountTextField.becomeFirstResponder() + } + // MARK: - Keyboard notifications @objc private func keyboardWillShow(_ notification: Notification) { @@ -160,9 +170,7 @@ class LoginViewController: UIViewController, UITextFieldDelegate, RootContainmen }, receiveValue: { _ in }) } - @IBAction func openCreateAccount() { - UIApplication.shared.open(WebLinks.createAccountURL, options: [:]) - } + @IBAction func openCreateAccount() {} // MARK: - Private diff --git a/ios/MullvadVPN/RootContainerViewController.swift b/ios/MullvadVPN/RootContainerViewController.swift index 786b705dc8..6b47847538 100644 --- a/ios/MullvadVPN/RootContainerViewController.swift +++ b/ios/MullvadVPN/RootContainerViewController.swift @@ -31,6 +31,9 @@ protocol RootContainment { /// Return the preferred header bar style var preferredHeaderBarStyle: HeaderBarStyle { get } + /// Return true if the view controller prefers header bar hidden + var prefersHeaderBarHidden: Bool { get } + } /// A root container class that primarily handles the unwind storyboard segues on log out @@ -49,6 +52,7 @@ class RootContainerViewController: UIViewController { @IBOutlet var transitionContainer: UIView! private(set) var headerBarStyle = HeaderBarStyle.default + private(set) var headerBarHidden = false override var childForStatusBarStyle: UIViewController? { return topViewController @@ -122,12 +126,44 @@ class RootContainerViewController: UIViewController { let animated = UIView.areAnimationsEnabled - setViewControllers(newViewControllers, animated: animated) + setViewControllersInternal(newViewControllers, isUnwinding: true, animated: animated) } // MARK: - Public - func setViewControllers(_ newViewControllers: [UIViewController], animated: Bool, completion: CompletionHandler? = nil) { + func setViewControllers(_ newViewControllers: [UIViewController], + animated: Bool, + completion: CompletionHandler? = nil) + { + setViewControllersInternal( + newViewControllers, + isUnwinding: false, + animated: animated, + completion: completion + ) + } + + func pushViewController(_ viewController: UIViewController, animated: Bool) { + var newViewControllers = viewControllers.filter({ $0 != viewController }) + newViewControllers.append(viewController) + + setViewControllersInternal(newViewControllers, isUnwinding: false, animated: animated) + } + + /// Request the root container to query the top controller for the new header bar style + func updateHeaderBarAppearance() { + updateHeaderBarStyleFromChildPreferences(animated: UIView.areAnimationsEnabled) + } + + // MARK: - Actions + + @IBAction func doShowSettings() { + performSegue(withIdentifier: SegueIdentifier.Root.showSettings.rawValue, sender: self) + } + + // MARK: - Private + + private func setViewControllersInternal(_ newViewControllers: [UIViewController], isUnwinding: Bool, animated: Bool, completion: CompletionHandler? = nil) { // Dot not handle appearance events when the container itself is not visible let shouldHandleAppearanceEvents = view.window != nil @@ -171,13 +207,7 @@ class RootContainerViewController: UIViewController { let alongSideAnimations = { self.updateHeaderBarStyleFromChildPreferences(animated: shouldAnimate) - } - - // Make sure that all new view controllers have loaded their views - // This is important because the unwind segue calls the unwind action which may rely on - // IB outlets to be set at that time. - for newViewController in newViewControllers { - newViewController.loadViewIfNeeded() + self.updateHeaderBarHiddenFromChildPreferences(animated: shouldAnimate) } // Add new child controllers. The call to addChild() automatically calls child.willMove() @@ -188,6 +218,13 @@ class RootContainerViewController: UIViewController { addChild(child) } + // Make sure that all new view controllers have loaded their views + // This is important because the unwind segue calls the unwind action which may rely on + // IB outlets to be set at that time. + for newViewController in newViewControllers { + newViewController.loadViewIfNeeded() + } + // Add the destination view into the view hierarchy if let targetView = targetViewController?.view { addChildView(targetView) @@ -226,7 +263,7 @@ class RootContainerViewController: UIViewController { case (.some(let lhs), .some(let rhs)): transition.subtype = lhs > rhs ? .fromLeft : .fromRight case (.none, .some): - transition.subtype = .fromLeft + transition.subtype = isUnwinding ? .fromLeft : .fromRight default: transition.subtype = .fromRight } @@ -241,26 +278,6 @@ class RootContainerViewController: UIViewController { } } - func pushViewController(_ viewController: UIViewController, animated: Bool) { - var newViewControllers = viewControllers.filter({ $0 != viewController }) - newViewControllers.append(viewController) - - setViewControllers(newViewControllers, animated: animated) - } - - /// Request the root container to query the top controller for the new header bar style - func updateHeaderBarAppearance() { - updateHeaderBarStyleFromChildPreferences(animated: UIView.areAnimationsEnabled) - } - - // MARK: - Actions - - @IBAction func doShowSettings() { - performSegue(withIdentifier: SegueIdentifier.Root.showSettings.rawValue, sender: self) - } - - // MARK: - Private - private func addChildView(_ childView: UIView) { childView.translatesAutoresizingMaskIntoConstraints = true childView.autoresizingMask = [.flexibleWidth, .flexibleHeight] @@ -284,11 +301,17 @@ class RootContainerViewController: UIViewController { /// Updates additional safe area insets to push the child views below the header bar private func updateAdditionalSafeAreaInsetsIfNeeded() { - var safeAreaInstes = additionalSafeAreaInsets - safeAreaInstes.top = headerBarView.frame.height + var safeAreaInsets = additionalSafeAreaInsets - if additionalSafeAreaInsets != safeAreaInstes { - additionalSafeAreaInsets = safeAreaInstes + // Reset top inset if header bar is invisible + if headerBarHidden { + safeAreaInsets.top = 0 + } else { + safeAreaInsets.top = headerBarView.frame.height + } + + if additionalSafeAreaInsets != safeAreaInsets { + additionalSafeAreaInsets = safeAreaInsets } } @@ -306,6 +329,20 @@ class RootContainerViewController: UIViewController { } } + private func setHeaderBarHidden(_ hidden: Bool, animated: Bool) { + headerBarHidden = hidden + + let action = { + self.headerBarView.alpha = hidden ? 0 : 1 + } + + if animated { + UIView.animate(withDuration: 0.25, animations: action) + } else { + action() + } + } + private func updateHeaderBarBackground() { headerBarView.backgroundColor = headerBarStyle.backgroundColor() } @@ -316,6 +353,12 @@ class RootContainerViewController: UIViewController { } } + private func updateHeaderBarHiddenFromChildPreferences(animated: Bool) { + if let conforming = topViewController as? RootContainment { + setHeaderBarHidden(conforming.prefersHeaderBarHidden, animated: animated) + } + } + } class RootContainerPushSegue: UIStoryboardSegue { diff --git a/ios/MullvadVPN/UIColor+Palette.swift b/ios/MullvadVPN/UIColor+Palette.swift index 8f6d35c147..8235f857a3 100644 --- a/ios/MullvadVPN/UIColor+Palette.swift +++ b/ios/MullvadVPN/UIColor+Palette.swift @@ -30,6 +30,12 @@ extension UIColor { } } + enum AppButton { + static let normalTitleColor = UIColor.white + static let highlightedTitleColor = UIColor.lightGray + static let disabledTitleColor = UIColor.lightGray + } + // Relay availability indicator view enum RelayStatusIndicator { static let activeColor = successColor.withAlphaComponent(0.9) diff --git a/ios/MullvadVPN/ViewControllerIdentifier.swift b/ios/MullvadVPN/ViewControllerIdentifier.swift index 8c4c9b2691..7e2dcd4e03 100644 --- a/ios/MullvadVPN/ViewControllerIdentifier.swift +++ b/ios/MullvadVPN/ViewControllerIdentifier.swift @@ -9,6 +9,7 @@ import Foundation enum ViewControllerIdentifier: String { + case consent = "Consent" case root = "Root" case login = "Login" case main = "Main" diff --git a/ios/MullvadVPN/WebLinks.swift b/ios/MullvadVPN/WebLinks.swift deleted file mode 100644 index cae8169f17..0000000000 --- a/ios/MullvadVPN/WebLinks.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// WebLinks.swift -// MullvadVPN -// -// Created by pronebird on 21/05/2019. -// Copyright © 2019 Mullvad VPN AB. All rights reserved. -// - -import Foundation - -enum WebLinks {} - -extension WebLinks { - - static let createAccountURL = URL(string: "https://mullvad.net/account/create/")! - static let purchaseURL = URL(string: "https://mullvad.net/account/login/")! - static let faqURL = URL(string: "https://mullvad.net/faq/")! - static let manageKeysURL = URL(string: "https://mullvad.net/en/account/ports/")! - -} diff --git a/ios/MullvadVPN/WireguardKeysViewController.swift b/ios/MullvadVPN/WireguardKeysViewController.swift index 9200698c32..70d9ab13bc 100644 --- a/ios/MullvadVPN/WireguardKeysViewController.swift +++ b/ios/MullvadVPN/WireguardKeysViewController.swift @@ -107,10 +107,6 @@ class WireguardKeysViewController: UIViewController { verifyKey(accountToken: accountToken, publicKey: publicKey) } - @IBAction func handleManageKeys(_ sender: Any) { - UIApplication.shared.open(WebLinks.manageKeysURL, options: [:]) - } - // MARK: - Private private func formatKeyGenerationElapsedTime(with creationDate: Date) -> String? { |
