diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2019-05-22 13:14:36 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2019-05-28 12:06:59 +0200 |
| commit | 1bfe1454b8149d76978d8118700910c6ee4faab7 (patch) | |
| tree | 8b71a657048c84257c54af56fde600545b0751bb | |
| parent | 1874d48758875482be85005bc3b7734bef4e2d35 (diff) | |
| download | mullvadvpn-1bfe1454b8149d76978d8118700910c6ee4faab7.tar.xz mullvadvpn-1bfe1454b8149d76978d8118700910c6ee4faab7.zip | |
Add account view
34 files changed, 1221 insertions, 357 deletions
diff --git a/ios/AdditionalAssets/DangerButton.svg b/ios/AdditionalAssets/DangerButton.svg new file mode 100644 index 0000000000..3230578e05 --- /dev/null +++ b/ios/AdditionalAssets/DangerButton.svg @@ -0,0 +1,3 @@ +<svg viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg"> + <rect x="0" y="0" width="44" height="44" rx="4" ry="4" fill="rgb(208, 2, 27)" /> +</svg> diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index dfd261a7fb..fe1e9919b8 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -9,9 +9,17 @@ /* Begin PBXBuildFile section */ 08A905C56DDB0A0A887BB5F5 /* Pods_MullvadVPN.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F506AB938C45AEB812886B4 /* Pods_MullvadVPN.framework */; }; 543203592C79FAD36BC1E700 /* Pods_PacketTunnel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A931E0F6F380B4B3FD45D987 /* Pods_PacketTunnel.framework */; }; + 581CBCE62296B97300727D7F /* ViewControllerIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581CBCE52296B97300727D7F /* ViewControllerIdentifier.swift */; }; + 581CBCEC2298041B00727D7F /* SettingsAppVersionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581CBCEB2298041B00727D7F /* SettingsAppVersionCell.swift */; }; + 581CBCEE229826FD00727D7F /* StaticTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581CBCED229826FD00727D7F /* StaticTableViewDataSource.swift */; }; + 582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1AE229566420055B6EF /* SettingsCell.swift */; }; + 582BB1B1229569620055B6EF /* CustomNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B0229569620055B6EF /* CustomNavigationBar.swift */; }; + 582BB1B3229574F40055B6EF /* SettingsAccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */; }; + 582BB1B52295780F0055B6EF /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B42295780F0055B6EF /* AccountExpiry.swift */; }; 58461AD3228D622E00B72ECB /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58461AD2228D622E00B72ECB /* Account.swift */; }; 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */; }; 5867A51C2248F26A005513C0 /* SegueIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867A51B2248F26A005513C0 /* SegueIdentifier.swift */; }; + 587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587425C02299833500CA2045 /* RootContainerViewController.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 */; }; @@ -19,8 +27,8 @@ 5888AD83227B11080051EB06 /* SelectLocationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5888AD82227B11080051EB06 /* SelectLocationCell.swift */; }; 5888AD87227B17950051EB06 /* SelectLocationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5888AD86227B17950051EB06 /* SelectLocationController.swift */; }; 5888AD89227B18C40051EB06 /* RelayList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5888AD88227B18C40051EB06 /* RelayList.swift */; }; + 5894FC492296A8090017471D /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5894FC482296A8090017471D /* CustomButton.swift */; }; 589AB4F7227B64450039131E /* BasicTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 589AB4F6227B64450039131E /* BasicTableViewCell.swift */; }; - 589AB4F9227C50D80039131E /* CustomNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 589AB4F8227C50D80039131E /* CustomNavigationBar.swift */; }; 58ADDB3C227B1BD200FAFEA7 /* JsonRpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ADDB3B227B1BD200FAFEA7 /* JsonRpc.swift */; }; 58ADDB3E227B1CD900FAFEA7 /* MullvadAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ADDB3D227B1CD900FAFEA7 /* MullvadAPI.swift */; }; 58ADDB40227B1E7100FAFEA7 /* Optional+Unwrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ADDB3F227B1E7100FAFEA7 /* Optional+Unwrap.swift */; }; @@ -40,7 +48,6 @@ 58F19E31228B2AEB00C7710B /* JSONRequestProcedure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F19E30228B2AEB00C7710B /* JSONRequestProcedure.swift */; }; 58F19E33228B383300C7710B /* AccountVerificationProcedure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F19E32228B383300C7710B /* AccountVerificationProcedure.swift */; }; 58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */; }; - 58F37E7D2243ECCB00C75C97 /* HeaderBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F37E7C2243ECCB00C75C97 /* HeaderBarViewController.swift */; }; 58FFE444228C82A00036F391 /* UserDefaultsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FFE443228C82A00036F391 /* UserDefaultsInteractor.swift */; }; /* End PBXBuildFile section */ @@ -71,10 +78,18 @@ /* Begin PBXFileReference section */ 3F506AB938C45AEB812886B4 /* Pods_MullvadVPN.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MullvadVPN.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 40D54D8A8B75B67DC37C5CCE /* Pods-PacketTunnel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PacketTunnel.release.xcconfig"; path = "Target Support Files/Pods-PacketTunnel/Pods-PacketTunnel.release.xcconfig"; sourceTree = "<group>"; }; + 581CBCE52296B97300727D7F /* ViewControllerIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerIdentifier.swift; sourceTree = "<group>"; }; + 581CBCEB2298041B00727D7F /* SettingsAppVersionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppVersionCell.swift; sourceTree = "<group>"; }; + 581CBCED229826FD00727D7F /* StaticTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticTableViewDataSource.swift; sourceTree = "<group>"; }; + 582BB1AE229566420055B6EF /* SettingsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = "<group>"; }; + 582BB1B0229569620055B6EF /* CustomNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationBar.swift; sourceTree = "<group>"; }; + 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountCell.swift; sourceTree = "<group>"; }; + 582BB1B42295780F0055B6EF /* AccountExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiry.swift; sourceTree = "<group>"; }; 58461AD2228D622E00B72ECB /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; }; 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>"; }; + 587425C02299833500CA2045 /* RootContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootContainerViewController.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>"; }; @@ -82,8 +97,8 @@ 5888AD82227B11080051EB06 /* SelectLocationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationCell.swift; sourceTree = "<group>"; }; 5888AD86227B17950051EB06 /* SelectLocationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationController.swift; sourceTree = "<group>"; }; 5888AD88227B18C40051EB06 /* RelayList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayList.swift; sourceTree = "<group>"; }; + 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>"; }; - 589AB4F8227C50D80039131E /* CustomNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationBar.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>"; }; 58ADDB3F227B1E7100FAFEA7 /* Optional+Unwrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Unwrap.swift"; sourceTree = "<group>"; }; @@ -107,7 +122,6 @@ 58F19E30228B2AEB00C7710B /* JSONRequestProcedure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONRequestProcedure.swift; sourceTree = "<group>"; }; 58F19E32228B383300C7710B /* AccountVerificationProcedure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVerificationProcedure.swift; sourceTree = "<group>"; }; 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerActivityIndicatorView.swift; sourceTree = "<group>"; }; - 58F37E7C2243ECCB00C75C97 /* HeaderBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderBarViewController.swift; sourceTree = "<group>"; }; 58FFE443228C82A00036F391 /* UserDefaultsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsInteractor.swift; sourceTree = "<group>"; }; 627D4CE562B85202FCFA0EB1 /* Pods-MullvadVPN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MullvadVPN.debug.xcconfig"; path = "Target Support Files/Pods-MullvadVPN/Pods-MullvadVPN.debug.xcconfig"; sourceTree = "<group>"; }; 9F1362F46063B1D06EB0C685 /* Pods-PacketTunnel.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PacketTunnel.debug.xcconfig"; path = "Target Support Files/Pods-PacketTunnel/Pods-PacketTunnel.debug.xcconfig"; sourceTree = "<group>"; }; @@ -168,6 +182,7 @@ isa = PBXGroup; children = ( 58461AD2228D622E00B72ECB /* Account.swift */, + 582BB1B42295780F0055B6EF /* AccountExpiry.swift */, 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */, 58CCA01D2242787B004F3011 /* AccountTextField.swift */, 58F19E32228B383300C7710B /* AccountVerificationProcedure.swift */, @@ -176,8 +191,8 @@ 58CE5E6A224146210008646E /* Assets.xcassets */, 589AB4F6227B64450039131E /* BasicTableViewCell.swift */, 58CCA00F224249A1004F3011 /* ConnectViewController.swift */, - 589AB4F8227C50D80039131E /* CustomNavigationBar.swift */, - 58F37E7C2243ECCB00C75C97 /* HeaderBarViewController.swift */, + 5894FC482296A8090017471D /* CustomButton.swift */, + 582BB1B0229569620055B6EF /* CustomNavigationBar.swift */, 58CE5E6F224146210008646E /* Info.plist */, 58F19E30228B2AEB00C7710B /* JSONRequestProcedure.swift */, 58ADDB3B227B1BD200FAFEA7 /* JsonRpc.swift */, @@ -190,15 +205,21 @@ 58ADDB3F227B1E7100FAFEA7 /* Optional+Unwrap.swift */, 5888AD88227B18C40051EB06 /* RelayList.swift */, 5888AD7E2279B6BF0051EB06 /* RelayStatusIndicatorView.swift */, + 587425C02299833500CA2045 /* RootContainerViewController.swift */, 5867A51B2248F26A005513C0 /* SegueIdentifier.swift */, 5888AD82227B11080051EB06 /* SelectLocationCell.swift */, 5888AD86227B17950051EB06 /* SelectLocationController.swift */, + 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */, + 581CBCEB2298041B00727D7F /* SettingsAppVersionCell.swift */, + 582BB1AE229566420055B6EF /* SettingsCell.swift */, 58CCA01122424D11004F3011 /* SettingsViewController.swift */, 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */, + 581CBCED229826FD00727D7F /* StaticTableViewDataSource.swift */, 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */, 587CBFE222807F530028DED3 /* UIColor+Helpers.swift */, 58CCA0152242560B004F3011 /* UIColor+Palette.swift */, 58FFE443228C82A00036F391 /* UserDefaultsInteractor.swift */, + 581CBCE52296B97300727D7F /* ViewControllerIdentifier.swift */, 587B08E1229460C1000E6F17 /* WebLinks.swift */, ); path = MullvadVPN; @@ -405,31 +426,38 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 582BB1B52295780F0055B6EF /* AccountExpiry.swift in Sources */, + 582BB1B3229574F40055B6EF /* SettingsAccountCell.swift in Sources */, 58CCA010224249A1004F3011 /* ConnectViewController.swift in Sources */, + 581CBCE62296B97300727D7F /* ViewControllerIdentifier.swift in Sources */, + 582BB1B1229569620055B6EF /* CustomNavigationBar.swift in Sources */, 58461AD3228D622E00B72ECB /* Account.swift in Sources */, 5888AD87227B17950051EB06 /* SelectLocationController.swift in Sources */, 58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */, 58ADDB40227B1E7100FAFEA7 /* Optional+Unwrap.swift in Sources */, 58CCA0162242560B004F3011 /* UIColor+Palette.swift in Sources */, 587CBFE322807F530028DED3 /* UIColor+Helpers.swift in Sources */, - 589AB4F9227C50D80039131E /* CustomNavigationBar.swift in Sources */, + 581CBCEC2298041B00727D7F /* SettingsAppVersionCell.swift in Sources */, 587B08E2229460C1000E6F17 /* WebLinks.swift in Sources */, 58CCA01822426713004F3011 /* AccountViewController.swift in Sources */, 58ADDB3E227B1CD900FAFEA7 /* MullvadAPI.swift in Sources */, 587B08E0229433EB000E6F17 /* LoginState.swift in Sources */, + 582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */, 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */, 5888AD83227B11080051EB06 /* SelectLocationCell.swift in Sources */, 58CE5E66224146200008646E /* LoginViewController.swift in Sources */, 58ADDB3C227B1BD200FAFEA7 /* JsonRpc.swift in Sources */, + 581CBCEE229826FD00727D7F /* StaticTableViewDataSource.swift in Sources */, 58CE5E64224146200008646E /* AppDelegate.swift in Sources */, 58CCA01222424D11004F3011 /* SettingsViewController.swift in Sources */, 58F19E33228B383300C7710B /* AccountVerificationProcedure.swift in Sources */, - 58F37E7D2243ECCB00C75C97 /* HeaderBarViewController.swift in Sources */, 589AB4F7227B64450039131E /* BasicTableViewCell.swift in Sources */, 5888AD7F2279B6BF0051EB06 /* RelayStatusIndicatorView.swift in Sources */, 5867A51C2248F26A005513C0 /* SegueIdentifier.swift in Sources */, 58F19E31228B2AEB00C7710B /* JSONRequestProcedure.swift in Sources */, + 5894FC492296A8090017471D /* CustomButton.swift in Sources */, 58CCA01E2242787B004F3011 /* AccountTextField.swift in Sources */, + 587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */, 58FFE444228C82A00036F391 /* UserDefaultsInteractor.swift in Sources */, 5888AD89227B18C40051EB06 /* RelayList.swift in Sources */, 58C3A4B222456F1B00340BDB /* AccountInputGroupView.swift in Sources */, diff --git a/ios/MullvadVPN/Account.swift b/ios/MullvadVPN/Account.swift index ef340d9339..2c006dd8c5 100644 --- a/ios/MullvadVPN/Account.swift +++ b/ios/MullvadVPN/Account.swift @@ -17,10 +17,24 @@ class Account { case invalidAccount } + /// Returns the currently used account token + static var token: String? { + return UserDefaultsInteractor.sharedApplicationGroupInteractor.accountToken + } + + /// Returns the account expiry for the currently used account token + static var expiry: Date? { + return UserDefaultsInteractor.sharedApplicationGroupInteractor.accountExpiry + } + + static var isLoggedIn: Bool { + return token != nil + } + /// Perform the login and save the account token along with expiry (if available) to the /// application preferences. class func login(with accountToken: String) -> Procedure { - let userDefaultsInteractor = UserDefaultsInteractor.withApplicationGroupUserDefaults() + let userDefaultsInteractor = UserDefaultsInteractor.sharedApplicationGroupInteractor // Request account token verification let verificationProcedure = AccountVerificationProcedure(accountToken: accountToken) @@ -49,7 +63,7 @@ class Account { /// Perform the logout by erasing the account token and expiry from the application preferences. class func logout() { - let userDefaultsInteractor = UserDefaultsInteractor.withApplicationGroupUserDefaults() + let userDefaultsInteractor = UserDefaultsInteractor.sharedApplicationGroupInteractor userDefaultsInteractor.accountToken = nil userDefaultsInteractor.accountExpiry = nil diff --git a/ios/MullvadVPN/AccountExpiry.swift b/ios/MullvadVPN/AccountExpiry.swift new file mode 100644 index 0000000000..493de6dc5b --- /dev/null +++ b/ios/MullvadVPN/AccountExpiry.swift @@ -0,0 +1,39 @@ +// +// AccountExpiry.swift +// MullvadVPN +// +// Created by pronebird on 22/05/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import Foundation + +class AccountExpiry { + let date: Date + + private lazy var relativeFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .full + formatter.allowedUnits = [.minute, .hour, .day, .month, .year] + formatter.maximumUnitCount = 1 + + return formatter + }() + + init(date: Date) { + self.date = date + } + + var isExpired: Bool { + return date < Date() + } + + var formattedRemainingTime: String { + return relativeFormatter.string(from: Date(), to: date)! + } + + var formattedDate: String { + return DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .medium) + } + +} diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift index 626ec1eb4a..b9f5f02e7f 100644 --- a/ios/MullvadVPN/AccountViewController.swift +++ b/ios/MullvadVPN/AccountViewController.swift @@ -10,9 +10,42 @@ import UIKit class AccountViewController: UIViewController { + @IBOutlet var accountLabel: UILabel! + @IBOutlet var expiryLabel: UILabel! + override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. + + updateView() + } + + // MARK: - Actions + + @IBAction func doBuyCredits() { + UIApplication.shared.open(WebLinks.purchaseURL, options: [:]) + } + + @IBAction func doLogout() { + Account.logout() + + performSegue(withIdentifier: SegueIdentifier.Account.logout.rawValue, sender: self) } + // MARK: - Private + + private func updateView() { + accountLabel.text = Account.token + + if let expiryDate = Account.expiry { + let accountExpiry = AccountExpiry(date: expiryDate) + + if accountExpiry.isExpired { + expiryLabel.text = NSLocalizedString("OUT OF TIME", tableName: "Settings", comment: "") + expiryLabel.textColor = .dangerColor + } else { + expiryLabel.text = accountExpiry.formattedDate + expiryLabel.textColor = .white + } + } + } } diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 07b96942b9..94f04ef9c4 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -13,9 +13,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? + let mainStoryboard = UIStoryboard(name: "Main", bundle: nil) func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + let rootViewController = window?.rootViewController as! RootContainerViewController + let loginViewController = mainStoryboard.instantiateViewController(withIdentifier: ViewControllerIdentifier.login.rawValue) + + var viewControllers = [UIViewController]() + viewControllers.append(loginViewController) + + if Account.isLoggedIn { + let mainViewController = mainStoryboard.instantiateViewController(withIdentifier: ViewControllerIdentifier.main.rawValue) + + viewControllers.append(mainViewController) + } + + rootViewController.setViewControllers(viewControllers, animated: false) + return true } @@ -41,6 +55,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - } diff --git a/ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/Contents.json new file mode 100644 index 0000000000..58a332d0b6 --- /dev/null +++ b/ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "DangerButton.pdf", + "resizing" : { + "mode" : "9-part", + "center" : { + "mode" : "tile", + "width" : 1, + "height" : 1 + }, + "cap-insets" : { + "bottom" : 4, + "top" : 4, + "right" : 4, + "left" : 4 + } + } + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +}
\ No newline at end of file diff --git a/ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/DangerButton.pdf b/ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/DangerButton.pdf Binary files differnew file mode 100644 index 0000000000..c1f95daf3b --- /dev/null +++ b/ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/DangerButton.pdf diff --git a/ios/MullvadVPN/Assets.xcassets/DefaultButton.imageset/DefaultButton.pdf b/ios/MullvadVPN/Assets.xcassets/DefaultButton.imageset/DefaultButton.pdf Binary files differindex ab05d02bb7..efae69e9a3 100644 --- a/ios/MullvadVPN/Assets.xcassets/DefaultButton.imageset/DefaultButton.pdf +++ b/ios/MullvadVPN/Assets.xcassets/DefaultButton.imageset/DefaultButton.pdf diff --git a/ios/MullvadVPN/Assets.xcassets/IconExtlink.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/IconExtlink.imageset/Contents.json index e1f118207d..a5f30fcb66 100644 --- a/ios/MullvadVPN/Assets.xcassets/IconExtlink.imageset/Contents.json +++ b/ios/MullvadVPN/Assets.xcassets/IconExtlink.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } }
\ No newline at end of file diff --git a/ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/SuccessButton.pdf b/ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/SuccessButton.pdf Binary files differindex 3d9f601cd0..fb318000c5 100644 --- a/ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/SuccessButton.pdf +++ b/ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/SuccessButton.pdf diff --git a/ios/MullvadVPN/Assets.xcassets/TranslucentDangerButton.imageset/TranslucentDangerButton.pdf b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerButton.imageset/TranslucentDangerButton.pdf Binary files differindex a69b22fb31..dc8dc8d705 100644 --- a/ios/MullvadVPN/Assets.xcassets/TranslucentDangerButton.imageset/TranslucentDangerButton.pdf +++ b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerButton.imageset/TranslucentDangerButton.pdf diff --git a/ios/MullvadVPN/Assets.xcassets/TranslucentNeutralButton.imageset/TranslucentNeutralButton.pdf b/ios/MullvadVPN/Assets.xcassets/TranslucentNeutralButton.imageset/TranslucentNeutralButton.pdf Binary files differindex 82ee77fd9a..b4f5b3b123 100644 --- a/ios/MullvadVPN/Assets.xcassets/TranslucentNeutralButton.imageset/TranslucentNeutralButton.pdf +++ b/ios/MullvadVPN/Assets.xcassets/TranslucentNeutralButton.imageset/TranslucentNeutralButton.pdf diff --git a/ios/MullvadVPN/Base.lproj/Main.storyboard b/ios/MullvadVPN/Base.lproj/Main.storyboard index 8402640d4a..7162a8a2c5 100644 --- a/ios/MullvadVPN/Base.lproj/Main.storyboard +++ b/ios/MullvadVPN/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r"> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="aW6-TO-kM3"> <device id="retina4_7" orientation="portrait"> <adaptation id="fullscreen"/> </device> @@ -9,29 +9,88 @@ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> + <!--Root Container View Controller--> + <scene sceneID="euw-TF-Dd7"> + <objects> + <viewController id="aW6-TO-kM3" customClass="RootContainerViewController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController"> + <view key="view" contentMode="scaleToFill" id="hQl-Sx-c1J"> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LEj-gs-rBL"> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> + <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <viewLayoutGuide key="safeArea" id="Uo1-pA-GWn"/> + </view> + <view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cw4-px-5hC"> + <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"/> + </imageView> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uXv-Tf-PET"> + <rect key="frame" x="327" y="44.5" width="24" height="24"/> + <state key="normal" image="IconSettings"/> + <connections> + <segue destination="Kqv-qu-mfF" kind="presentation" identifier="ShowSettings" id="cxu-NC-VeP"/> + </connections> + </button> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="MULLVAD VPN" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dqy-A0-TdV"> + <rect key="frame" x="69" y="42" width="168" height="29"/> + <fontDescription key="fontDescription" type="boldSystem" pointSize="24"/> + <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + </subviews> + <color key="backgroundColor" red="0.16078431369999999" green="0.30196078430000001" blue="0.45098039220000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <constraints> + <constraint firstAttribute="trailingMargin" secondItem="uXv-Tf-PET" secondAttribute="trailing" id="1LM-Tg-1Kr"/> + <constraint firstItem="cKg-hE-JsS" firstAttribute="centerY" secondItem="dqy-A0-TdV" secondAttribute="centerY" id="IT0-VO-msz"/> + <constraint firstAttribute="bottomMargin" secondItem="dqy-A0-TdV" secondAttribute="bottom" constant="22" id="YTk-xg-wIk"/> + <constraint firstItem="uXv-Tf-PET" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="dqy-A0-TdV" secondAttribute="trailing" constant="8" symbolic="YES" id="ZEb-xZ-1ga"/> + <constraint firstItem="uXv-Tf-PET" firstAttribute="centerY" secondItem="dqy-A0-TdV" secondAttribute="centerY" id="gCl-OS-ONw"/> + <constraint firstItem="cKg-hE-JsS" firstAttribute="leading" secondItem="cw4-px-5hC" secondAttribute="leadingMargin" constant="-12" id="hGJ-yd-hnp"/> + <constraint firstItem="dqy-A0-TdV" firstAttribute="top" secondItem="cw4-px-5hC" secondAttribute="topMargin" constant="22" id="mMF-ha-mRO"/> + <constraint firstItem="dqy-A0-TdV" firstAttribute="leading" secondItem="cKg-hE-JsS" secondAttribute="trailing" constant="8" id="q8s-25-ASt"/> + </constraints> + <edgeInsets key="layoutMargins" top="20" left="24" bottom="0.0" right="24"/> + </view> + </subviews> + <color key="backgroundColor" red="0.16078431369999999" green="0.30196078430000001" blue="0.45098039220000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <constraints> + <constraint firstItem="LEj-gs-rBL" firstAttribute="top" secondItem="hQl-Sx-c1J" secondAttribute="top" id="22p-PG-rk7"/> + <constraint firstItem="LEj-gs-rBL" firstAttribute="leading" secondItem="hQl-Sx-c1J" secondAttribute="leading" id="Ehw-ts-TGl"/> + <constraint firstAttribute="trailing" secondItem="cw4-px-5hC" secondAttribute="trailing" id="RE4-jS-w03"/> + <constraint firstAttribute="bottom" secondItem="LEj-gs-rBL" secondAttribute="bottom" id="hXO-xN-pdV"/> + <constraint firstItem="cw4-px-5hC" firstAttribute="top" secondItem="hQl-Sx-c1J" secondAttribute="top" id="k5T-fl-TkJ"/> + <constraint firstAttribute="trailing" secondItem="LEj-gs-rBL" secondAttribute="trailing" id="o8y-zm-Y1o"/> + <constraint firstItem="cw4-px-5hC" firstAttribute="leading" secondItem="hQl-Sx-c1J" secondAttribute="leading" id="u7B-kz-QQZ"/> + </constraints> + <viewLayoutGuide key="safeArea" id="gDr-jW-ZhR"/> + </view> + <connections> + <outlet property="headerBarSettingsButton" destination="uXv-Tf-PET" id="psX-Ij-AXM"/> + <outlet property="headerBarView" destination="cw4-px-5hC" id="lBM-gg-xZq"/> + <outlet property="transitionContainer" destination="LEj-gs-rBL" id="WHt-LX-Uwu"/> + </connections> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="nyE-nN-nlq" userLabel="First Responder" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="-430" y="27"/> + </scene> <!--Login View Controller--> <scene sceneID="tne-QT-ifu"> <objects> - <viewController id="BYZ-38-t0r" customClass="LoginViewController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController"> + <viewController storyboardIdentifier="Login" id="BYZ-38-t0r" customClass="LoginViewController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController"> <view key="view" contentMode="scaleToFill" id="xpu-Q8-m8b"> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <subviews> - <containerView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VIF-P4-vZU" userLabel="Header"> - <rect key="frame" x="0.0" y="20" width="375" height="74"/> - <color key="backgroundColor" red="0.16078431369999999" green="0.30196078430000001" blue="0.45098039220000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> - <constraints> - <constraint firstAttribute="height" constant="74" id="ohH-N4-eN7"/> - </constraints> - <connections> - <segue destination="rCI-6x-aLd" kind="embed" identifier="EmbedHeaderBar" id="tVd-Lw-FVU"/> - </connections> - </containerView> <view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0ZY-Kh-JiM" userLabel="Container"> - <rect key="frame" x="0.0" y="94" width="375" height="573"/> + <rect key="frame" x="0.0" y="20" width="375" height="647"/> <subviews> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pID-oa-Rrg" customClass="SpinnerActivityIndicatorView" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="163.5" y="125.5" width="48" height="48"/> + <rect key="frame" x="163.5" y="162.5" width="48" height="48"/> <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <constraints> <constraint firstAttribute="height" constant="48" id="2J4-Qc-ctc"/> @@ -39,10 +98,10 @@ </constraints> </view> <imageView clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.0" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="IconSuccess" translatesAutoresizingMaskIntoConstraints="NO" id="7ux-Tb-Fzq"> - <rect key="frame" x="157.5" y="119.5" width="60" height="60"/> + <rect key="frame" x="157.5" y="156.5" width="60" height="60"/> </imageView> <view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="V3j-Lb-fSQ" userLabel="Form"> - <rect key="frame" x="0.0" y="203.5" width="375" height="126"/> + <rect key="frame" x="0.0" y="240.5" width="375" height="126"/> <subviews> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Login" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Nxn-Fc-EGe"> <rect key="frame" x="24" y="0.0" width="327" height="39"/> @@ -147,11 +206,8 @@ <constraint firstAttribute="bottom" secondItem="0ZY-Kh-JiM" secondAttribute="bottom" id="09L-EV-qfI"/> <constraint firstItem="0ZY-Kh-JiM" firstAttribute="leading" secondItem="xpu-Q8-m8b" secondAttribute="leading" id="5T5-Un-Bbw"/> <constraint firstItem="Ire-2z-eJu" firstAttribute="leading" secondItem="xpu-Q8-m8b" secondAttribute="leading" id="8MY-2T-1p9"/> - <constraint firstItem="VIF-P4-vZU" firstAttribute="top" secondItem="RSb-dJ-fKl" secondAttribute="top" id="A7p-Wh-bUn"/> - <constraint firstItem="RSb-dJ-fKl" firstAttribute="trailing" secondItem="VIF-P4-vZU" secondAttribute="trailing" id="YPn-KH-Dy5"/> + <constraint firstItem="0ZY-Kh-JiM" firstAttribute="top" secondItem="RSb-dJ-fKl" secondAttribute="top" id="XTe-ZF-Txi"/> <constraint firstAttribute="trailing" secondItem="0ZY-Kh-JiM" secondAttribute="trailing" id="ZCl-FF-h79"/> - <constraint firstItem="VIF-P4-vZU" firstAttribute="leading" secondItem="RSb-dJ-fKl" secondAttribute="leading" id="bkX-Cb-jYl"/> - <constraint firstItem="0ZY-Kh-JiM" firstAttribute="top" secondItem="VIF-P4-vZU" secondAttribute="bottom" id="d2t-dv-LwM"/> <constraint firstAttribute="bottom" secondItem="Ire-2z-eJu" secondAttribute="bottom" id="okj-M8-3PQ"/> <constraint firstAttribute="trailing" secondItem="Ire-2z-eJu" secondAttribute="trailing" id="uZQ-0R-5JT"/> </constraints> @@ -169,8 +225,7 @@ <outlet property="messageLabel" destination="XSV-Lk-dj4" id="8bd-TU-yhD"/> <outlet property="statusImageView" destination="7ux-Tb-Fzq" id="UhU-kS-PKR"/> <outlet property="titleLabel" destination="Nxn-Fc-EGe" id="lOm-uS-4Ff"/> - <segue destination="Ki6-Mt-b6R" kind="presentation" identifier="ShowConnect" animates="NO" id="ccw-Nc-l0Q"/> - <segue destination="Kqv-qu-mfF" kind="presentation" identifier="ShowSettings" id="RjC-Wk-Enk"/> + <segue destination="Ki6-Mt-b6R" kind="custom" identifier="ShowConnect" customClass="RootContainerPushSegue" customModule="MullvadVPN" customModuleProvider="target" id="ccw-Nc-l0Q"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> @@ -197,36 +252,27 @@ <!--Connect View Controller--> <scene sceneID="Fnf-X9-B7i"> <objects> - <viewController id="Ki6-Mt-b6R" customClass="ConnectViewController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController"> + <viewController storyboardIdentifier="Main" id="Ki6-Mt-b6R" customClass="ConnectViewController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController"> <view key="view" contentMode="scaleToFill" id="PNd-mm-N1B"> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <containerView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C7a-Bl-BmL"> - <rect key="frame" x="0.0" y="20" width="375" height="74"/> - <constraints> - <constraint firstAttribute="height" constant="74" id="Rif-54-EA1"/> - </constraints> - <connections> - <segue destination="rCI-6x-aLd" kind="embed" identifier="EmbedHeaderBar" id="qKR-6L-kfz"/> - </connections> - </containerView> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="MapBackground" translatesAutoresizingMaskIntoConstraints="NO" id="3Ck-JT-ogd"> - <rect key="frame" x="0.0" y="94" width="375" height="573"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="667.00000000000023"/> </imageView> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="shY-Lj-oYx"> - <rect key="frame" x="24" y="533" width="327" height="110"/> + <rect key="frame" x="24" y="543" width="327" height="100"/> <subviews> <visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JHO-Ca-Zzd" customClass="TranslucentButtonBlurView" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="0.0" width="327" height="50"/> + <rect key="frame" x="0.0" y="0.0" width="327" height="42"/> <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="jVH-JP-pJo"> - <rect key="frame" x="0.0" y="0.0" width="327" height="50"/> + <rect key="frame" x="0.0" y="0.0" width="327" height="42"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hVz-q0-Xpd"> - <rect key="frame" x="0.0" y="0.0" width="327" height="50"/> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hVz-q0-Xpd" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="0.0" y="0.0" width="327" height="42"/> <fontDescription key="fontDescription" type="system" pointSize="20"/> - <inset key="contentEdgeInsets" minX="0.0" minY="10" maxX="0.0" maxY="10"/> + <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"/> </state> @@ -242,12 +288,17 @@ <constraint firstItem="hVz-q0-Xpd" firstAttribute="top" secondItem="jVH-JP-pJo" secondAttribute="top" id="UY2-bK-i8s"/> </constraints> </view> + <constraints> + <constraint firstAttribute="height" constant="42" id="Doo-X5-rCc"/> + </constraints> <blurEffect style="light"/> </visualEffectView> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vxU-Mt-fMo"> - <rect key="frame" x="0.0" y="66" width="327" height="44"/> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vxU-Mt-fMo" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="0.0" y="58" width="327" height="42"/> + <constraints> + <constraint firstAttribute="height" constant="42" placeholder="YES" id="C9s-Bs-hA5"/> + </constraints> <fontDescription key="fontDescription" type="system" pointSize="20"/> - <inset key="contentEdgeInsets" minX="0.0" minY="10" maxX="0.0" maxY="10"/> <state key="normal" title="Secure my connection" backgroundImage="SuccessButton"> <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> </state> @@ -258,68 +309,108 @@ <color key="backgroundColor" red="0.16078431369999999" green="0.30196078430000001" blue="0.45098039220000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <constraints> <constraint firstAttribute="trailingMargin" secondItem="shY-Lj-oYx" secondAttribute="trailing" id="2y6-PR-9e3"/> - <constraint firstItem="C7a-Bl-BmL" firstAttribute="top" secondItem="iBo-pG-OTz" secondAttribute="top" id="DAc-is-w0k"/> + <constraint firstItem="3Ck-JT-ogd" firstAttribute="top" secondItem="PNd-mm-N1B" secondAttribute="top" id="EBE-j1-lqr"/> <constraint firstItem="3Ck-JT-ogd" firstAttribute="leading" secondItem="PNd-mm-N1B" secondAttribute="leading" id="OEK-r4-gE7"/> - <constraint firstItem="3Ck-JT-ogd" firstAttribute="top" secondItem="C7a-Bl-BmL" secondAttribute="bottom" id="Rzf-01-pyd"/> <constraint firstAttribute="bottom" secondItem="3Ck-JT-ogd" secondAttribute="bottom" id="e75-bI-sYJ"/> <constraint firstAttribute="trailing" secondItem="3Ck-JT-ogd" secondAttribute="trailing" id="fXi-Cn-1bF"/> - <constraint firstItem="C7a-Bl-BmL" firstAttribute="leading" secondItem="iBo-pG-OTz" secondAttribute="leading" id="jx2-nb-cxY"/> <constraint firstAttribute="bottomMargin" secondItem="shY-Lj-oYx" secondAttribute="bottom" id="rYu-al-UqH"/> - <constraint firstItem="iBo-pG-OTz" firstAttribute="trailing" secondItem="C7a-Bl-BmL" secondAttribute="trailing" id="rig-zZ-55o"/> <constraint firstItem="shY-Lj-oYx" firstAttribute="leading" secondItem="PNd-mm-N1B" secondAttribute="leadingMargin" id="v5s-FD-Iaw"/> </constraints> <edgeInsets key="layoutMargins" top="0.0" left="24" bottom="24" right="24"/> <viewLayoutGuide key="safeArea" id="iBo-pG-OTz"/> </view> - <connections> - <segue destination="Kqv-qu-mfF" kind="presentation" identifier="ShowSettings" id="fxZ-Uq-nxv"/> - </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="gkg-dm-hcG" userLabel="First Responder" sceneMemberID="firstResponder"/> </objects> - <point key="canvasLocation" x="1920.8" y="26.53673163418291"/> + <point key="canvasLocation" x="1690" y="27"/> </scene> <!--Settings--> <scene sceneID="3oF-uu-3Bk"> <objects> <tableViewController id="SHd-a4-ewi" customClass="SettingsViewController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController"> - <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="6Gz-UM-orK"> + <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="6Gz-UM-orK"> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <color key="backgroundColor" red="0.098039215690000001" green="0.18039215689999999" blue="0.27058823529999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <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" textLabel="EgF-AZ-LLU" detailTextLabel="OtL-Zd-v9V" style="IBUITableViewCellStyleValue1" id="ghE-jC-RWf"> - <rect key="frame" x="0.0" y="28" width="375" height="44"/> + <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="44"/> <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="341" height="43.5"/> <autoresizingMask key="autoresizingMask"/> <subviews> - <label opaque="NO" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Account" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="EgF-AZ-LLU"> - <rect key="frame" x="16" y="12" width="63.5" height="20.5"/> - <autoresizingMask key="autoresizingMask"/> + <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="22"/> <fontDescription key="fontDescription" type="system" pointSize="17"/> - <nil key="textColor"/> + <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> </label> - <label opaque="NO" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="5 days left" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="OtL-Zd-v9V"> - <rect key="frame" x="260" y="12" width="80" height="20.5"/> - <autoresizingMask key="autoresizingMask"/> - <fontDescription key="fontDescription" type="system" pointSize="17"/> - <nil key="textColor"/> + <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="252" y="11" width="81" height="22"/> + <fontDescription key="fontDescription" type="system" weight="medium" pointSize="13"/> + <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> </label> </subviews> + <constraints> + <constraint firstItem="Lve-Kd-qTr" firstAttribute="bottom" secondItem="sTl-gI-g2a" secondAttribute="bottomMargin" id="2nF-fr-JAQ"/> + <constraint firstItem="Lve-Kd-qTr" firstAttribute="top" secondItem="sTl-gI-g2a" secondAttribute="topMargin" id="2se-fh-l9F"/> + <constraint firstItem="QeD-EQ-Ruo" firstAttribute="top" secondItem="sTl-gI-g2a" secondAttribute="topMargin" id="6Cf-jR-RQ2"/> + <constraint firstItem="QeD-EQ-Ruo" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Lve-Kd-qTr" secondAttribute="trailing" id="Axr-Q2-gFq"/> + <constraint firstAttribute="bottomMargin" secondItem="QeD-EQ-Ruo" secondAttribute="bottom" id="VES-Yv-Ull"/> + <constraint firstItem="QeD-EQ-Ruo" firstAttribute="trailing" secondItem="sTl-gI-g2a" secondAttribute="trailingMargin" id="bMl-dk-MoO"/> + <constraint firstItem="Lve-Kd-qTr" firstAttribute="leading" secondItem="sTl-gI-g2a" secondAttribute="leadingMargin" id="yrm-Np-m0P"/> + </constraints> </tableViewCellContentView> + <color key="backgroundColor" red="0.16078431369999999" green="0.30196078430000001" blue="0.45098039220000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <connections> + <outlet property="expiryLabel" destination="QeD-EQ-Ruo" id="sr0-cQ-JV1"/> + <outlet property="titleLabel" destination="Lve-Kd-qTr" id="psd-kM-u1u"/> <segue destination="ruh-Q2-P39" kind="show" id="Oei-D9-z6L"/> </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="99.5" width="375" height="44"/> + <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.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="22"/> + <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="22"/> + <fontDescription key="fontDescription" type="system" weight="medium" pointSize="13"/> + <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + </subviews> + <constraints> + <constraint firstItem="pYC-Zb-8N9" firstAttribute="top" secondItem="lYp-Z8-1sN" secondAttribute="topMargin" id="6Ih-SA-8o0"/> + <constraint firstAttribute="bottomMargin" secondItem="sOr-vj-cg7" secondAttribute="bottom" id="8Gv-HC-Rxo"/> + <constraint firstItem="sOr-vj-cg7" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="pYC-Zb-8N9" secondAttribute="trailing" constant="8" symbolic="YES" id="IYX-rX-a2Q"/> + <constraint firstAttribute="trailingMargin" secondItem="sOr-vj-cg7" secondAttribute="trailing" id="Is4-dU-mbu"/> + <constraint firstAttribute="bottomMargin" secondItem="pYC-Zb-8N9" secondAttribute="bottom" id="NpP-d6-M0T"/> + <constraint firstItem="pYC-Zb-8N9" firstAttribute="leading" secondItem="lYp-Z8-1sN" secondAttribute="leadingMargin" id="Ove-uA-2Fw"/> + <constraint firstItem="sOr-vj-cg7" firstAttribute="top" secondItem="lYp-Z8-1sN" secondAttribute="topMargin" id="qeA-c2-OxT"/> + </constraints> + </tableViewCellContentView> + <color key="backgroundColor" red="0.16078431369999999" green="0.30196078430000001" blue="0.45098039220000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <connections> + <outlet property="versionLabel" destination="sOr-vj-cg7" id="xgH-No-26f"/> + </connections> + </tableViewCell> </prototypes> <sections/> <connections> - <outlet property="dataSource" destination="SHd-a4-ewi" id="qys-E3-WsY"/> - <outlet property="delegate" destination="SHd-a4-ewi" id="Fwe-NL-Bgt"/> + <outlet property="dataSource" destination="9xf-6a-8vR" id="DSW-6u-Rhl"/> + <outlet property="delegate" destination="9xf-6a-8vR" id="HBx-xF-dDg"/> </connections> </tableView> <navigationItem key="navigationItem" title="Settings" id="Xxl-r7-Sbm"> @@ -329,10 +420,14 @@ </connections> </barButtonItem> </navigationItem> + <connections> + <outlet property="staticDataSource" destination="9xf-6a-8vR" id="E8j-Z4-Ljk"/> + </connections> </tableViewController> <placeholder placeholderIdentifier="IBFirstResponder" id="sR5-ix-4x7" userLabel="First Responder" sceneMemberID="firstResponder"/> + <customObject id="9xf-6a-8vR" customClass="SettingsTableViewDataSource" customModule="MullvadVPN" customModuleProvider="target"/> </objects> - <point key="canvasLocation" x="4105" y="27"/> + <point key="canvasLocation" x="1690" y="-832"/> </scene> <!--Account--> <scene sceneID="Ca0-W1-eLb"> @@ -341,22 +436,173 @@ <view key="view" contentMode="scaleToFill" id="Qpl-bL-ZGl"> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <subviews> + <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="saE-dV-AgF"> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> + <subviews> + <view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rkG-Xa-pEO" userLabel="Container"> + <rect key="frame" x="0.0" y="0.0" width="375" height="279"/> + <subviews> + <view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nkx-Eb-7le" userLabel="Content"> + <rect key="frame" x="24" y="24" width="327" height="247"/> + <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="45.5"/> + <subviews> + <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="5ux-jY-AC5"> + <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="Account number" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0L8-AT-A51"> + <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"/> + <nil key="highlightedColor"/> + </label> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="123456789" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jvc-8m-jM5"> + <rect key="frame" x="0.0" y="25" width="327" height="20.5"/> + <fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/> + <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + </subviews> + </stackView> + </subviews> + <constraints> + <constraint firstAttribute="trailing" secondItem="5ux-jY-AC5" secondAttribute="trailing" id="PJ0-He-QSB"/> + <constraint firstAttribute="bottom" secondItem="5ux-jY-AC5" secondAttribute="bottom" id="Uxx-Cj-ONf"/> + <constraint firstItem="5ux-jY-AC5" firstAttribute="top" secondItem="HzF-8Z-UBs" secondAttribute="top" id="gnz-tE-Rri"/> + <constraint firstItem="5ux-jY-AC5" firstAttribute="leading" secondItem="HzF-8Z-UBs" secondAttribute="leading" id="n4M-ed-uC3"/> + </constraints> + </view> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="459-0n-9V2" userLabel="Expiry"> + <rect key="frame" x="0.0" y="69.5" width="327" height="45.5"/> + <subviews> + <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"> + <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"/> + <nil key="highlightedColor"/> + </label> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="May 16, 2019" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8Vg-dd-ZpW"> + <rect key="frame" x="0.0" y="25" width="327" height="20.5"/> + <fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/> + <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + </subviews> + </stackView> + </subviews> + <constraints> + <constraint firstItem="NMg-f0-BTW" firstAttribute="top" secondItem="459-0n-9V2" secondAttribute="top" id="JQm-gX-yM4"/> + <constraint firstAttribute="trailing" secondItem="NMg-f0-BTW" secondAttribute="trailing" id="VtX-r0-IfB"/> + <constraint firstAttribute="bottom" secondItem="NMg-f0-BTW" secondAttribute="bottom" id="XJw-2J-qBl"/> + <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" 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" buttonType="roundedRect" 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" buttonType="roundedRect" 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"/> + <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> + <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"/> + </constraints> + </view> + </subviews> + <constraints> + <constraint firstAttribute="trailing" secondItem="Acd-vw-Pu7" secondAttribute="trailing" id="AzF-Vm-XaC"/> + <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="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> + </view> + </subviews> + <constraints> + <constraint firstAttribute="bottomMargin" secondItem="nkx-Eb-7le" secondAttribute="bottom" id="28V-SW-noS"/> + <constraint firstAttribute="trailingMargin" secondItem="nkx-Eb-7le" secondAttribute="trailing" id="KQB-PO-stg"/> + <constraint firstItem="nkx-Eb-7le" firstAttribute="leading" secondItem="rkG-Xa-pEO" secondAttribute="leadingMargin" id="L4C-cS-yzC"/> + <constraint firstItem="nkx-Eb-7le" firstAttribute="top" secondItem="rkG-Xa-pEO" secondAttribute="topMargin" id="eea-1e-zMf"/> + </constraints> + </view> + </subviews> + <constraints> + <constraint firstAttribute="bottom" secondItem="rkG-Xa-pEO" secondAttribute="bottom" id="B6s-Tv-NQF"/> + <constraint firstItem="rkG-Xa-pEO" firstAttribute="leading" secondItem="saE-dV-AgF" secondAttribute="leading" id="FeG-FO-jRU"/> + <constraint firstItem="rkG-Xa-pEO" firstAttribute="width" secondItem="saE-dV-AgF" secondAttribute="width" id="Vai-Jc-iRg"/> + <constraint firstItem="rkG-Xa-pEO" firstAttribute="top" secondItem="saE-dV-AgF" secondAttribute="top" id="guJ-dt-tsQ"/> + <constraint firstAttribute="trailing" secondItem="rkG-Xa-pEO" secondAttribute="trailing" id="xas-S1-tKp"/> + </constraints> + </scrollView> + </subviews> + <color key="backgroundColor" red="0.098039215690000001" green="0.18039215689999999" blue="0.27058823529999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <constraints> + <constraint firstAttribute="bottom" secondItem="saE-dV-AgF" secondAttribute="bottom" id="Ldq-tX-ami"/> + <constraint firstAttribute="trailing" secondItem="saE-dV-AgF" secondAttribute="trailing" id="jaQ-Ns-Hja"/> + <constraint firstItem="saE-dV-AgF" firstAttribute="top" secondItem="Qpl-bL-ZGl" secondAttribute="top" id="sZ0-CC-Onn"/> + <constraint firstItem="saE-dV-AgF" firstAttribute="leading" secondItem="Qpl-bL-ZGl" secondAttribute="leading" id="xba-Jt-Ulk"/> + </constraints> + <edgeInsets key="layoutMargins" top="24" left="24" bottom="24" right="24"/> <viewLayoutGuide key="safeArea" id="jrJ-di-3DV"/> </view> <navigationItem key="navigationItem" title="Account" id="rL3-Y8-3g8"/> + <connections> + <outlet property="accountLabel" destination="jvc-8m-jM5" id="HnU-i4-BRj"/> + <outlet property="expiryLabel" destination="8Vg-dd-ZpW" id="3n5-2Z-J8y"/> + <segue destination="P2i-eG-jQx" kind="unwind" identifier="Logout" unwindAction="unwindFromAccountWithSegue:" id="5li-wk-yRM"/> + </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="3tt-67-nI8" userLabel="First Responder" sceneMemberID="firstResponder"/> + <exit id="P2i-eG-jQx" userLabel="Exit" sceneMemberID="exit"/> </objects> - <point key="canvasLocation" x="5062" y="24"/> + <point key="canvasLocation" x="2649" y="-834"/> </scene> <!--Navigation Controller--> <scene sceneID="er3-W2-NkS"> <objects> <navigationController id="Kqv-qu-mfF" sceneMemberID="viewController"> - <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="7PK-0x-byW"> - <rect key="frame" x="0.0" y="20" width="375" height="44"/> + <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="7PK-0x-byW" customClass="CustomNavigationBar" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="0.0" y="20" width="375" height="96"/> <autoresizingMask key="autoresizingMask"/> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> </navigationBar> <connections> <segue destination="SHd-a4-ewi" kind="relationship" relationship="rootViewController" id="5n8-Yk-l4C"/> @@ -364,15 +610,19 @@ </navigationController> <placeholder placeholderIdentifier="IBFirstResponder" id="bHt-Id-Zc4" userLabel="First Responder" sceneMemberID="firstResponder"/> </objects> - <point key="canvasLocation" x="3084" y="27"/> + <point key="canvasLocation" x="670" y="-832"/> </scene> <!--Navigation Controller--> <scene sceneID="oT4-Ap-qrZ"> <objects> <navigationController id="hOC-Ab-N3D" sceneMemberID="viewController"> - <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" id="kmu-Ab-x1c" customClass="CustomNavigationBar" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="20" width="375" height="44"/> + <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="kmu-Ab-x1c" customClass="CustomNavigationBar" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="0.0" y="20" width="375" height="96"/> <autoresizingMask key="autoresizingMask"/> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <textAttributes key="largeTitleTextAttributes"> + <offsetWrapper key="textShadowOffset" horizontal="0.0" vertical="0.0"/> + </textAttributes> </navigationBar> <connections> <segue destination="FxZ-7F-3yi" kind="relationship" relationship="rootViewController" id="cFv-eb-G19"/> @@ -380,54 +630,7 @@ </navigationController> <placeholder placeholderIdentifier="IBFirstResponder" id="GCK-Z5-Jwh" userLabel="First Responder" sceneMemberID="firstResponder"/> </objects> - <point key="canvasLocation" x="1260" y="841"/> - </scene> - <!--Header Bar View Controller--> - <scene sceneID="XNS-uo-8Yd"> - <objects> - <viewController definesPresentationContext="YES" id="rCI-6x-aLd" customClass="HeaderBarViewController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController"> - <view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="cw4-px-5hC"> - <rect key="frame" x="0.0" y="0.0" width="375" height="74"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <subviews> - <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LogoIcon" translatesAutoresizingMaskIntoConstraints="NO" id="cKg-hE-JsS"> - <rect key="frame" x="11" y="29" width="16" height="16"/> - </imageView> - <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uXv-Tf-PET"> - <rect key="frame" x="343" y="26" width="16" height="22"/> - <state key="normal" image="IconSettings"/> - <connections> - <action selector="handleSettingsButton" destination="rCI-6x-aLd" eventType="touchUpInside" id="TaM-cZ-TvJ"/> - </connections> - </button> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="MULLVAD VPN" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dqy-A0-TdV"> - <rect key="frame" x="35" y="22" width="168" height="30"/> - <fontDescription key="fontDescription" type="boldSystem" pointSize="24"/> - <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> - <nil key="highlightedColor"/> - </label> - </subviews> - <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> - <constraints> - <constraint firstAttribute="trailingMargin" secondItem="uXv-Tf-PET" secondAttribute="trailing" id="1LM-Tg-1Kr"/> - <constraint firstItem="cKg-hE-JsS" firstAttribute="centerY" secondItem="cw4-px-5hC" secondAttribute="centerY" id="WVK-1I-XmT"/> - <constraint firstAttribute="bottom" secondItem="dqy-A0-TdV" secondAttribute="bottom" constant="22" id="YTk-xg-wIk"/> - <constraint firstItem="uXv-Tf-PET" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="dqy-A0-TdV" secondAttribute="trailing" constant="8" symbolic="YES" id="ZEb-xZ-1ga"/> - <constraint firstItem="cKg-hE-JsS" firstAttribute="leading" secondItem="cw4-px-5hC" secondAttribute="leadingMargin" constant="-5" id="hGJ-yd-hnp"/> - <constraint firstItem="dqy-A0-TdV" firstAttribute="top" secondItem="cw4-px-5hC" secondAttribute="top" constant="22" id="mMF-ha-mRO"/> - <constraint firstItem="dqy-A0-TdV" firstAttribute="leading" secondItem="cKg-hE-JsS" secondAttribute="trailing" constant="8" id="q8s-25-ASt"/> - <constraint firstItem="uXv-Tf-PET" firstAttribute="centerY" secondItem="cw4-px-5hC" secondAttribute="centerY" id="vEf-j6-cTF"/> - </constraints> - <edgeInsets key="layoutMargins" top="0.0" left="0.0" bottom="0.0" right="0.0"/> - <viewLayoutGuide key="safeArea" id="oeE-aF-UYv"/> - </view> - <connections> - <outlet property="settingsButton" destination="uXv-Tf-PET" id="MuL-Bu-ZRF"/> - </connections> - </viewController> - <placeholder placeholderIdentifier="IBFirstResponder" id="Kbx-AI-gkv" userLabel="First Responder" sceneMemberID="firstResponder"/> - </objects> - <point key="canvasLocation" x="1326" y="-509"/> + <point key="canvasLocation" x="1690" y="841"/> </scene> <!--Select location--> <scene sceneID="Kar-Ys-a6u"> @@ -439,37 +642,28 @@ <color key="backgroundColor" red="0.098039215690000001" green="0.18039215689999999" blue="0.27058823529999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="separatorColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <view key="tableHeaderView" contentMode="scaleToFill" id="YMi-O0-jT1"> - <rect key="frame" x="0.0" y="0.0" width="375" height="159"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="145"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <subviews> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" text="Select location" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sfD-OR-Col"> - <rect key="frame" x="28" y="28" width="335" height="29"/> - <fontDescription key="fontDescription" type="system" weight="semibold" 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" verticalCompressionResistancePriority="751" text="While connected, your real location is masked with a private and secure location in the selected region" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="X0P-N8-lda"> - <rect key="frame" x="28" y="61" width="335" height="74"/> - <fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="17"/> + <rect key="frame" x="24" y="44" width="327" height="77"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> <color key="textColor" white="1" alpha="0.60217786815068497" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> </label> </subviews> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <constraints> - <constraint firstItem="X0P-N8-lda" firstAttribute="top" secondItem="sfD-OR-Col" secondAttribute="bottom" constant="4" id="7AH-h4-POM"/> <constraint firstAttribute="bottomMargin" secondItem="X0P-N8-lda" secondAttribute="bottom" id="Ghh-mK-nAy"/> - <constraint firstItem="sfD-OR-Col" firstAttribute="top" secondItem="YMi-O0-jT1" secondAttribute="topMargin" id="NVJ-TA-Aqw"/> <constraint firstAttribute="trailingMargin" secondItem="X0P-N8-lda" secondAttribute="trailing" id="gRy-Wb-s8K"/> - <constraint firstItem="sfD-OR-Col" firstAttribute="leading" secondItem="YMi-O0-jT1" secondAttribute="leadingMargin" id="r7f-jn-HXz"/> + <constraint firstItem="X0P-N8-lda" firstAttribute="top" secondItem="YMi-O0-jT1" secondAttribute="topMargin" id="mHY-Fb-HcE"/> <constraint firstItem="X0P-N8-lda" firstAttribute="leading" secondItem="YMi-O0-jT1" secondAttribute="leadingMargin" id="s3I-Rw-1Jg"/> - <constraint firstAttribute="trailingMargin" secondItem="sfD-OR-Col" secondAttribute="trailing" id="up1-GL-fpb"/> </constraints> - <edgeInsets key="layoutMargins" top="8" left="28" bottom="24" right="12"/> + <edgeInsets key="layoutMargins" top="24" left="24" bottom="24" right="24"/> </view> <prototypes> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="aFz-H5-sPu" customClass="SelectLocationCell" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="187" width="375" height="44"/> + <rect key="frame" x="0.0" y="173" width="375" height="44"/> <autoresizingMask key="autoresizingMask"/> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="aFz-H5-sPu" id="6nQ-gT-vzf"> <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/> @@ -530,8 +724,7 @@ </connections> </tableView> <navigationItem key="navigationItem" title="Select location" largeTitleDisplayMode="always" id="PZM-r8-1Sb"> - <barButtonItem key="leftBarButtonItem" title="Item" image="IconClose" id="4T0-a3-Ce4"> - <color key="tintColor" white="1" alpha="0.39956121575342468" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="4T0-a3-Ce4"> <connections> <segue destination="6Lc-ZQ-E4P" kind="unwind" identifier="" unwindAction="unwindFromSelectLocationWithSegue:" id="gAz-uu-Whd"/> </connections> @@ -544,13 +737,14 @@ <placeholder placeholderIdentifier="IBFirstResponder" id="EvX-LH-gOg" userLabel="First Responder" sceneMemberID="firstResponder"/> <exit id="6Lc-ZQ-E4P" userLabel="Exit" sceneMemberID="exit"/> </objects> - <point key="canvasLocation" x="2117.5999999999999" y="840.62968515742136"/> + <point key="canvasLocation" x="2649" y="841"/> </scene> </scenes> <resources> + <image name="DangerButton" width="9" height="9"/> <image name="DefaultButton" width="9" height="9"/> <image name="IconChevronDown" width="24" height="24"/> - <image name="IconClose" width="24" height="24"/> + <image name="IconExtlink" width="16" height="16"/> <image name="IconSettings" width="24" height="24"/> <image name="IconSuccess" width="60" height="60"/> <image name="IconTick" width="24" height="24"/> @@ -559,8 +753,4 @@ <image name="SuccessButton" width="9" height="9"/> <image name="TranslucentNeutralButton" width="9" height="9"/> </resources> - <inferredMetricsTieBreakers> - <segue reference="tVd-Lw-FVU"/> - <segue reference="RjC-Wk-Enk"/> - </inferredMetricsTieBreakers> </document> diff --git a/ios/MullvadVPN/BasicTableViewCell.swift b/ios/MullvadVPN/BasicTableViewCell.swift index 88a573bb1a..3da0ad00ab 100644 --- a/ios/MullvadVPN/BasicTableViewCell.swift +++ b/ios/MullvadVPN/BasicTableViewCell.swift @@ -24,4 +24,5 @@ class BasicTableViewCell: UITableViewCell { backgroundColor = UIColor.clear contentView.backgroundColor = UIColor.clear } + } diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift index ca0fbe7415..d7b615b3fb 100644 --- a/ios/MullvadVPN/ConnectViewController.swift +++ b/ios/MullvadVPN/ConnectViewController.swift @@ -8,17 +8,14 @@ import UIKit -class ConnectViewController: UIViewController, HeaderBarViewControllerDelegate { +class ConnectViewController: UIViewController, RootContainment { override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if case .embedHeader? = SegueIdentifier.Connect.from(segue: segue) { - let headerBarController = segue.destination as? HeaderBarViewController - headerBarController?.delegate = self - } + var preferredHeaderBarStyle: HeaderBarStyle { + return .unsecured } override func viewDidLoad() { @@ -26,12 +23,6 @@ class ConnectViewController: UIViewController, HeaderBarViewControllerDelegate { // Do any additional setup after loading the view, typically from a nib. } - // MARK: - HeaderBarViewControllerDelegate - - func headerBarViewControllerShouldOpenSettings(_ controller: HeaderBarViewController) { - performSegue(withIdentifier: SegueIdentifier.Connect.showSettings.rawValue, sender: self) - } - // MARK: - Actions @IBAction func unwindFromSelectLocation(segue: UIStoryboardSegue) { diff --git a/ios/MullvadVPN/CustomButton.swift b/ios/MullvadVPN/CustomButton.swift new file mode 100644 index 0000000000..119403c91c --- /dev/null +++ b/ios/MullvadVPN/CustomButton.swift @@ -0,0 +1,65 @@ +// +// CustomButton.swift +// MullvadVPN +// +// Created by pronebird on 23/05/2019. +// Copyright © 2019 Amagicom 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) + titleLabel?.textColor = UIColor.white + } + + 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/CustomNavigationBar.swift b/ios/MullvadVPN/CustomNavigationBar.swift index 5ffd4bb4f9..7465de78f4 100644 --- a/ios/MullvadVPN/CustomNavigationBar.swift +++ b/ios/MullvadVPN/CustomNavigationBar.swift @@ -2,84 +2,31 @@ // CustomNavigationBar.swift // MullvadVPN // -// Created by pronebird on 03/05/2019. +// Created by pronebird on 22/05/2019. // Copyright © 2019 Amagicom AB. All rights reserved. // import UIKit class CustomNavigationBar: UINavigationBar { - private(set) var isBarVisible = false - private let emptyShadow = UIImage() + override init(frame: CGRect) { + super.init(frame: frame) - /// The blur view used internally by UINavigationBar - private var effectView: UIVisualEffectView? { - // Find the background view in the navigation bar view hierarchy - let backgroundView = subviews.first(where: { $0.description.starts(with: "<_UIBarBackground") }) - - // Find the blur view in the background view's view hierarchy - let backgroundEffectView = backgroundView?.subviews.first(where: { $0 is UIVisualEffectView }) - - return backgroundEffectView as? UIVisualEffectView - } - - /// The custom title view or the standard title label used internally by UINavigationBar - private var titleView: UIView? { - // Return the custom title view when it's set - if let customTitleView = topItem?.titleView { - return customTitleView - } - - // Find the content view inside of the navigation bar hierarchy - let contentView = subviews.first(where: { $0.description.starts(with: "<_UINavigationBarContentView") }) - - // Find the UILabel in the content view's subviews - return contentView?.subviews.first(where: { $0 is UILabel }) - } - - override func layoutSubviews() { - super.layoutSubviews() - - // UINavigationBar creates subviews dynamically, so make sure to reset the navigation bar state - setBarBackgroundVisibility(isBarVisible) - - // UINavigationBar tends to reset the title view opacity in response to layout changes - setTitleVisibility(isBarVisible) + commonInit() } - func setBarVisible(_ visible: Bool, animated: Bool) { - guard isBarVisible != visible else { return } - - isBarVisible = visible + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) - let action = { - self.setBarBackgroundVisibility(visible) - self.setTitleVisibility(visible) - } - - if animated { - UIView.animate(withDuration: 0.25, delay: 0, - options: [.beginFromCurrentState], - animations: action) - } else { - action() - } + commonInit() } - private func setBarBackgroundVisibility(_ visible: Bool) { - let backgroundEffectView = effectView - - if visible { - backgroundEffectView?.alpha = 1 - shadowImage = nil - } else { - backgroundEffectView?.alpha = 0 - shadowImage = emptyShadow - } + private func commonInit() { + var margins = layoutMargins + margins.left = 24 + margins.right = 24 + layoutMargins = margins } - private func setTitleVisibility(_ visible: Bool) { - titleView?.alpha = visible ? 1 : 0 - } } diff --git a/ios/MullvadVPN/HeaderBarViewController.swift b/ios/MullvadVPN/HeaderBarViewController.swift deleted file mode 100644 index b2ab3630ba..0000000000 --- a/ios/MullvadVPN/HeaderBarViewController.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// HeaderBarViewController.swift -// MullvadVPN -// -// Created by pronebird on 21/03/2019. -// Copyright © 2019 Amagicom AB. All rights reserved. -// - -import UIKit - -protocol HeaderBarViewControllerDelegate: class { - func headerBarViewControllerShouldOpenSettings(_ controller: HeaderBarViewController) -} - -class HeaderBarViewController: UIViewController { - weak var delegate: HeaderBarViewControllerDelegate? - - @IBOutlet var settingsButton: UIButton! - - @IBAction func handleSettingsButton() { - delegate?.headerBarViewControllerShouldOpenSettings(self) - } -} diff --git a/ios/MullvadVPN/LoginViewController.swift b/ios/MullvadVPN/LoginViewController.swift index e820413e94..7ae878a4cd 100644 --- a/ios/MullvadVPN/LoginViewController.swift +++ b/ios/MullvadVPN/LoginViewController.swift @@ -13,7 +13,7 @@ import os.log private let kMinimumAccountTokenLength = 10 private let kValidAccountTokenCharacterSet = CharacterSet(charactersIn: "01234567890") -class LoginViewController: UIViewController, HeaderBarViewControllerDelegate, UITextFieldDelegate { +class LoginViewController: UIViewController, UITextFieldDelegate, RootContainment { @IBOutlet var keyboardToolbar: UIToolbar! @IBOutlet var keyboardToolbarLoginButton: UIBarButtonItem! @@ -26,8 +26,6 @@ class LoginViewController: UIViewController, HeaderBarViewControllerDelegate, UI @IBOutlet var activityIndicator: SpinnerActivityIndicatorView! @IBOutlet var statusImageView: UIImageView! - private weak var headerBarController: HeaderBarViewController? - private let procedureQueue = ProcedureQueue() private var loginState = LoginState.default { didSet { @@ -39,11 +37,8 @@ class LoginViewController: UIViewController, HeaderBarViewControllerDelegate, UI return .lightContent } - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if case .embedHeader? = SegueIdentifier.Login.from(segue: segue) { - headerBarController = segue.destination as? HeaderBarViewController - headerBarController?.delegate = self - } + var preferredHeaderBarStyle: HeaderBarStyle { + return .transparent } override func viewDidLoad() { @@ -86,12 +81,6 @@ class LoginViewController: UIViewController, HeaderBarViewControllerDelegate, UI object: accountTextField) } - // MARK: - HeaderBarViewControllerDelegate - - func headerBarViewControllerShouldOpenSettings(_ controller: HeaderBarViewController) { - performSegue(withIdentifier: SegueIdentifier.Login.showSettings.rawValue, sender: self) - } - // MARK: - Keyboard notifications @objc private func keyboardWillShow(_ notification: Notification) { @@ -138,7 +127,13 @@ class LoginViewController: UIViewController, HeaderBarViewControllerDelegate, UI return string.unicodeScalars.allSatisfy { kValidAccountTokenCharacterSet.contains($0) } } - // MARK: - IBActions + // MARK: - Actions + + @IBAction func unwindFromAccount(segue: UIStoryboardSegue) { + loginState = .default + accountTextField.text = "" + updateKeyboardToolbar() + } @IBAction func cancelLogin() { view.endEditing(true) @@ -195,10 +190,10 @@ class LoginViewController: UIViewController, HeaderBarViewControllerDelegate, UI fallthrough case .success: - headerBarController?.settingsButton.isEnabled = false + rootContainerController?.headerBarSettingsButton.isEnabled = false case .default, .failure: - headerBarController?.settingsButton.isEnabled = true + rootContainerController?.headerBarSettingsButton.isEnabled = true activityIndicator.isAnimating = false } @@ -242,6 +237,8 @@ class LoginViewController: UIViewController, HeaderBarViewControllerDelegate, UI } else if case .success = loginState { // Navigate to the main view after 1s delay DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { + self.rootContainerController?.headerBarSettingsButton.isEnabled = true + self.performSegue(withIdentifier: SegueIdentifier.Login.showConnect.rawValue, sender: self) } diff --git a/ios/MullvadVPN/RootContainerViewController.swift b/ios/MullvadVPN/RootContainerViewController.swift new file mode 100644 index 0000000000..18fbb91302 --- /dev/null +++ b/ios/MullvadVPN/RootContainerViewController.swift @@ -0,0 +1,346 @@ +// +// RootContainerViewController.swift +// MullvadVPN +// +// Created by pronebird on 25/05/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import UIKit + +enum HeaderBarStyle { + case transparent, `default`, unsecured, secured + + fileprivate func backgroundColor() -> UIColor { + switch self { + case .transparent: + return UIColor.clear + case .default: + return UIColor.HeaderBar.defaultBackgroundColor + case .secured: + return UIColor.HeaderBar.securedBackgroundColor + case .unsecured: + return UIColor.HeaderBar.unsecuredBackgroundColor + } + } +} + +/// A protocol that defines the relationship between the root container and its child controllers +protocol RootContainment { + + /// Return the preferred header bar style + var preferredHeaderBarStyle: HeaderBarStyle { get } + +} + +/// A root container class that primarily handles the unwind storyboard segues on log out +class RootContainerViewController: UIViewController { + + typealias CompletionHandler = () -> Void + + private var viewControllers = [UIViewController]() + + private var topViewController: UIViewController? { + return viewControllers.last + } + + @IBOutlet var headerBarView: UIView! + @IBOutlet var headerBarSettingsButton: UIButton! + @IBOutlet var transitionContainer: UIView! + + private(set) var headerBarStyle = HeaderBarStyle.default + + override var childForStatusBarStyle: UIViewController? { + return topViewController + } + + override var childForStatusBarHidden: UIViewController? { + return topViewController + } + + override var shouldAutomaticallyForwardAppearanceMethods: Bool { + return false + } + + // MARK: - View lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + var margins = view.layoutMargins + margins.left = 24 + margins.right = 24 + view.layoutMargins = margins + + updateHeaderBarBackground() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + updateAdditionalSafeAreaInsetsIfNeeded() + } + + override func viewSafeAreaInsetsDidChange() { + super.viewSafeAreaInsetsDidChange() + + updateHeaderBarLayoutMarginsIfNeeded() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + topViewController?.beginAppearanceTransition(true, animated: animated) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + topViewController?.endAppearanceTransition() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + topViewController?.beginAppearanceTransition(false, animated: animated) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + topViewController?.endAppearanceTransition() + } + + // MARK: - Storyboard segue handling + + override func unwind(for unwindSegue: UIStoryboardSegue, towards subsequentVC: UIViewController) { + let index = viewControllers.firstIndex(of: subsequentVC)! + let newViewControllers = Array(viewControllers.prefix(through: index)) + + let animated = UIView.areAnimationsEnabled + + setViewControllers(newViewControllers, animated: animated) + } + + // MARK: - Public + + func setViewControllers(_ newViewControllers: [UIViewController], animated: Bool, completion: CompletionHandler? = nil) { + // Dot not handle appearance events when the container itself is not visible + let shouldHandleAppearanceEvents = view.window != nil + + // Animations won't run when the container is not visible, so prevent them + let shouldAnimate = animated && shouldHandleAppearanceEvents + + let sourceViewController = topViewController + let targetViewController = newViewControllers.last + + let viewControllersToAdd = newViewControllers.filter { !viewControllers.contains($0) } + let viewControllersToRemove = viewControllers.filter { !newViewControllers.contains($0) } + + let finishTransition = { + // Notify the added controllers that they finished a transition into the container + for child in viewControllersToAdd { + child.didMove(toParent: self) + } + + // Remove the controllers that transitioned out of the container + // The call to removeFromParent() automatically calls child.didMove() + for child in viewControllersToRemove { + child.view.removeFromSuperview() + child.removeFromParent() + } + + // Remove the source controller from view hierarchy + if sourceViewController != targetViewController { + sourceViewController?.view.removeFromSuperview() + } + + // Finish appearance transition + if shouldHandleAppearanceEvents { + sourceViewController?.endAppearanceTransition() + if sourceViewController != targetViewController { + targetViewController?.endAppearanceTransition() + } + } + + completion?() + } + + 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() + } + + // Add new child controllers. The call to addChild() automatically calls child.willMove() + // Children have to be registered in the container for Storyboard unwind segues to function + // properly, however the child controller views don't have to be added immediately, and + // appearance methods have to be handled manually. + for child in viewControllersToAdd { + addChild(child) + } + + // Add the destination view into the view hierarchy + if let targetView = targetViewController?.view { + addChildView(targetView) + } + + // Notify the controllers that they will transition out of the container + for child in viewControllersToRemove { + child.willMove(toParent: nil) + } + + viewControllers = newViewControllers + + // Begin appearance transition + if shouldHandleAppearanceEvents { + sourceViewController?.beginAppearanceTransition(false, animated: shouldAnimate) + if sourceViewController != targetViewController { + targetViewController?.beginAppearanceTransition(true, animated: shouldAnimate) + } + } + + if shouldAnimate { + CATransaction.begin() + CATransaction.setCompletionBlock { + finishTransition() + } + + let transition = CATransition() + transition.duration = 0.35 + transition.type = .push + + // Pick the animation movement direction + let sourceIndex = sourceViewController.flatMap({ newViewControllers.firstIndex(of: $0) }) + let targetIndex = targetViewController.flatMap({ newViewControllers.firstIndex(of: $0) }) + + switch (sourceIndex, targetIndex) { + case (.some(let lhs), .some(let rhs)): + transition.subtype = lhs > rhs ? .fromLeft : .fromRight + case (.none, .some): + transition.subtype = .fromLeft + default: + transition.subtype = .fromRight + } + + transitionContainer.layer.add(transition, forKey: "transition") + alongSideAnimations() + + CATransaction.commit() + } else { + alongSideAnimations() + finishTransition() + } + } + + 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 setNeedsHeaderBarStyleAppearance() { + 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] + childView.frame = transitionContainer.bounds + + transitionContainer.addSubview(childView) + } + + /// Updates the header bar's layout margins to make sure it doesn't go below the system status + /// bar. + private func updateHeaderBarLayoutMarginsIfNeeded() { + let offsetTop = view.safeAreaInsets.top - additionalSafeAreaInsets.top + + var layoutMargins = headerBarView.layoutMargins + layoutMargins.top = offsetTop + layoutMargins.left = view.layoutMargins.left + layoutMargins.right = view.layoutMargins.right + layoutMargins.bottom = 0 + + if layoutMargins != headerBarView.layoutMargins { + headerBarView.layoutMargins = layoutMargins + } + } + + /// 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 + + if additionalSafeAreaInsets != safeAreaInstes { + additionalSafeAreaInsets = safeAreaInstes + } + } + + private func setHeaderBarStyle(_ style: HeaderBarStyle, animated: Bool) { + headerBarStyle = style + + let action = { + self.updateHeaderBarBackground() + } + + if animated { + UIView.animate(withDuration: 0.25, animations: action) + } else { + action() + } + } + + private func updateHeaderBarBackground() { + headerBarView.backgroundColor = headerBarStyle.backgroundColor() + } + + private func updateHeaderBarStyleFromChildPreferences(animated: Bool) { + if let conforming = topViewController as? RootContainment { + setHeaderBarStyle(conforming.preferredHeaderBarStyle, animated: animated) + } + } + +} + +class RootContainerPushSegue: UIStoryboardSegue { + override func perform() { + let container = source.rootContainerController! + let animated = UIView.areAnimationsEnabled + + container.pushViewController(destination, animated: animated) + } +} + +extension UIViewController { + + var rootContainerController: RootContainerViewController? { + var viewController: UIViewController? = parent + + while viewController != nil { + if let container = viewController as? RootContainerViewController { + return container + } + + viewController = viewController?.parent + } + + return nil + } + +} diff --git a/ios/MullvadVPN/SegueIdentifier.swift b/ios/MullvadVPN/SegueIdentifier.swift index 536ab74717..d6cea1659b 100644 --- a/ios/MullvadVPN/SegueIdentifier.swift +++ b/ios/MullvadVPN/SegueIdentifier.swift @@ -11,14 +11,11 @@ import UIKit // A phantom struct holding the storyboard segue identifiers for each view controller struct SegueIdentifier { - enum Connect: String, SegueConvertible { - case embedHeader = "EmbedHeaderBar" + enum Root: String, SegueConvertible { case showSettings = "ShowSettings" } enum Login: String, SegueConvertible { - case embedHeader = "EmbedHeaderBar" - case showSettings = "ShowSettings" case showConnect = "ShowConnect" } @@ -26,6 +23,10 @@ struct SegueIdentifier { case returnToConnectWithNewRelay = "ReturnToConnectWithNewRelay" } + enum Account: String, SegueConvertible { + case logout = "Logout" + } + private init() {} } diff --git a/ios/MullvadVPN/SelectLocationCell.swift b/ios/MullvadVPN/SelectLocationCell.swift index 18adee51a8..4e0c536d8b 100644 --- a/ios/MullvadVPN/SelectLocationCell.swift +++ b/ios/MullvadVPN/SelectLocationCell.swift @@ -49,15 +49,8 @@ class SelectLocationCell: BasicTableViewCell { collapseButton.addTarget(self, action: #selector(handleCollapseButton(_ :)), for: .touchUpInside) updateCollapseImage() - } - - override func layoutMarginsDidChange() { - super.layoutMarginsDidChange() - // enforce the preferred layout margins - if contentView.layoutMargins != preferredMargins { - contentView.layoutMargins = preferredMargins - } + contentView.layoutMargins = preferredMargins } override func layoutSubviews() { diff --git a/ios/MullvadVPN/SelectLocationController.swift b/ios/MullvadVPN/SelectLocationController.swift index abb3f085f7..b6d50e39eb 100644 --- a/ios/MullvadVPN/SelectLocationController.swift +++ b/ios/MullvadVPN/SelectLocationController.swift @@ -28,13 +28,12 @@ class SelectLocationController: UITableViewController { super.viewDidLoad() loadRelayList() - updateTableHeaderViewSize(tableViewSize: tableView.frame.size) } - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() - updateTableHeaderViewSize(tableViewSize: size) + updateTableHeaderViewSizeIfNeeded() } // MARK: - UITableViewDataSource @@ -79,12 +78,6 @@ class SelectLocationController: UITableViewController { } } - // MARK: - UIScrollViewDelegate - - override func scrollViewDidScroll(_ scrollView: UIScrollView) { - updateBarVisibility(threshold: 12) - } - // MARK: - Relay list handling private func loadRelayList() { @@ -148,36 +141,26 @@ class SelectLocationController: UITableViewController { } } - // MARK: - Bar visibility - - private func updateBarVisibility(threshold: CGFloat) { - guard let navigationBar = navigationController?.navigationBar as? CustomNavigationBar else { - return - } - - let shouldShowBar = tableView.contentOffset.y > (-tableView.adjustedContentInset.top + threshold) - - navigationBar.setBarVisible(shouldShowBar, animated: true) - } - // MARK: - UITableView header - private func updateTableHeaderViewSize(tableViewSize: CGSize) { + private func updateTableHeaderViewSizeIfNeeded() { guard let header = tableView.tableHeaderView else { return } - // layout the header view - header.setNeedsLayout() - header.layoutIfNeeded() - // measure the view size let sizeConstraint = CGSize( - width: tableViewSize.width, + width: tableView.bounds.width, height: UIView.layoutFittingCompressedSize.height ) - header.frame.size = header.systemLayoutSizeFitting(sizeConstraint) - // reset the header view to force UITableView layout pass - tableView.tableHeaderView = header + let newSize = header.systemLayoutSizeFitting(sizeConstraint) + let oldSize = header.frame.size + + if oldSize.height != newSize.height { + header.frame.size.height = newSize.height + + // reset the header view to force UITableView layout pass + tableView.tableHeaderView = header + } } } diff --git a/ios/MullvadVPN/SettingsAccountCell.swift b/ios/MullvadVPN/SettingsAccountCell.swift new file mode 100644 index 0000000000..f938b26df2 --- /dev/null +++ b/ios/MullvadVPN/SettingsAccountCell.swift @@ -0,0 +1,50 @@ +// +// SettingsAccountCell.swift +// MullvadVPN +// +// Created by pronebird on 22/05/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import UIKit + +class SettingsAccountCell: SettingsCell { + + @IBOutlet var titleLabel: UILabel! + @IBOutlet var expiryLabel: UILabel! + + var accountExpiryDate: Date? { + didSet { + didUpdateAccountExpiry() + } + } + + override func awakeFromNib() { + super.awakeFromNib() + + // Remove the right margin since the accessory view adds it automatically + contentView.layoutMargins.right = 0 + } + + private func didUpdateAccountExpiry() { + if let accountExpiryDate = accountExpiryDate { + let accountExpiry = AccountExpiry(date: accountExpiryDate) + + if accountExpiry.isExpired { + expiryLabel.text = NSLocalizedString("OUT OF TIME", tableName: "Settings", comment: "") + expiryLabel.textColor = .dangerColor + } else { + let remainingTime = accountExpiry.formattedRemainingTime + let localizedString = NSLocalizedString("%@ left", tableName: "Settings", comment: "") + let formattedString = String(format: localizedString, remainingTime) + + expiryLabel.text = formattedString.uppercased() + expiryLabel.textColor = .white + } + } else { + expiryLabel.text = "" + expiryLabel.textColor = .white + } + } + +} diff --git a/ios/MullvadVPN/SettingsAppVersionCell.swift b/ios/MullvadVPN/SettingsAppVersionCell.swift new file mode 100644 index 0000000000..6e36716701 --- /dev/null +++ b/ios/MullvadVPN/SettingsAppVersionCell.swift @@ -0,0 +1,13 @@ +// +// SettingsAppVersionCell.swift +// MullvadVPN +// +// Created by pronebird on 24/05/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import UIKit + +class SettingsAppVersionCell: SettingsCell { + @IBOutlet var versionLabel: UILabel! +} diff --git a/ios/MullvadVPN/SettingsCell.swift b/ios/MullvadVPN/SettingsCell.swift new file mode 100644 index 0000000000..4d09e52651 --- /dev/null +++ b/ios/MullvadVPN/SettingsCell.swift @@ -0,0 +1,24 @@ +// +// SettingsCell.swift +// MullvadVPN +// +// Created by pronebird on 22/05/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import UIKit + +class SettingsCell: BasicTableViewCell { + + private let preferredMargins = UIEdgeInsets(top: 14, left: 24, bottom: 14, right: 12) + + override func awakeFromNib() { + super.awakeFromNib() + + backgroundView?.backgroundColor = UIColor.Cell.backgroundColor + selectedBackgroundView?.backgroundColor = UIColor.Cell.selectedAltBackgroundColor + + contentView.layoutMargins = preferredMargins + } + +} diff --git a/ios/MullvadVPN/SettingsViewController.swift b/ios/MullvadVPN/SettingsViewController.swift index af20b8dd11..88b1ef6555 100644 --- a/ios/MullvadVPN/SettingsViewController.swift +++ b/ios/MullvadVPN/SettingsViewController.swift @@ -7,14 +7,42 @@ // import UIKit - -private let kAccountCellIdentifier = "Account" +import Foundation class SettingsViewController: UITableViewController { + @IBOutlet var staticDataSource: SettingsTableViewDataSource! + + private enum CellIdentifier: String { + case account = "Account" + case appVersion = "AppVersion" + } + override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. + + if Account.isLoggedIn { + let topSection = StaticTableViewSection() + let accountRow = StaticTableViewRow(reuseIdentifier: CellIdentifier.account.rawValue) { (_, cell) in + let cell = cell as! SettingsAccountCell + + cell.accountExpiryDate = Account.expiry + } + topSection.addRows([accountRow]) + staticDataSource.addSections([topSection]) + } + + let middleSection = StaticTableViewSection() + let versionRow = StaticTableViewRow(reuseIdentifier: CellIdentifier.appVersion.rawValue) { (_, cell) in + let cell = cell as! SettingsAppVersionCell + let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + + cell.versionLabel.text = versionString + } + versionRow.isSelectable = false + + middleSection.addRows([versionRow]) + staticDataSource.addSections([middleSection]) } // MARK: - IBActions @@ -23,37 +51,18 @@ class SettingsViewController: UITableViewController { dismiss(animated: true) } - // MARK: - UITableViewDataSource +} - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - // TODO: implement - } +class SettingsTableViewDataSource: StaticTableViewDataSource { // MARK: - UITableViewDelegate - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - if indexPath.section == 0 { - switch indexPath.row { - case 0: - let cell = tableView.dequeueReusableCell(withIdentifier: kAccountCellIdentifier, for: indexPath) - - return cell - - default: - break - } - } - - fatalError("Index path \(indexPath) is not handled.") - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 24 } - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 1 + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return 0.01 } } diff --git a/ios/MullvadVPN/StaticTableViewDataSource.swift b/ios/MullvadVPN/StaticTableViewDataSource.swift new file mode 100644 index 0000000000..e9504ee567 --- /dev/null +++ b/ios/MullvadVPN/StaticTableViewDataSource.swift @@ -0,0 +1,92 @@ +// +// StaticTableViewDataSource.swift +// MullvadVPN +// +// Created by pronebird on 24/05/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import UIKit + +class StaticTableViewRow { + typealias ConfigurationBlock = (IndexPath, UITableViewCell) -> Void + typealias ActionBlock = (IndexPath) -> Void + + let reuseIdentifier: String + let configurationBlock: ConfigurationBlock + + var isSelectable = true + var isHidden = false + var actionBlock: ActionBlock? + + init(reuseIdentifier: String, configurationBlock: @escaping ConfigurationBlock) { + self.reuseIdentifier = reuseIdentifier + self.configurationBlock = configurationBlock + } +} + +class StaticTableViewSection { + private(set) var rows = [StaticTableViewRow]() + + var isHidden: Bool { + return rows.allSatisfy({ $0.isHidden }) + } + + func addRows(_ rows: [StaticTableViewRow]) { + self.rows.append(contentsOf: rows) + } +} + +class StaticTableViewDataSource: NSObject, UITableViewDataSource, UITableViewDelegate { + + private(set) var sections = [StaticTableViewSection]() + + func addSections(_ sections: [StaticTableViewSection]) { + self.sections.append(contentsOf: sections) + } + + // MARK: - UITableViewDelegate + + func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + let row = self.row(for: indexPath) + + return row.isSelectable + } + + // MARK: - UITableViewDataSource + + func numberOfSections(in tableView: UITableView) -> Int { + return sections.reduce(0, { $1.isHidden ? $0 : $0 + 1 }) + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return sections[section].rows.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let row = self.row(for: indexPath) + let reuseIdentifier = row.reuseIdentifier + + let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) + + row.configurationBlock(indexPath, cell) + + return cell + } + + // MARK: - Private + + private func row(for indexPath: IndexPath) -> StaticTableViewRow { + let section = self.section(for: indexPath) + let row = section.rows.compactMap({ $0.isHidden ? nil : $0 }) + + return row[indexPath.row] + } + + private func section(for indexPath: IndexPath) -> StaticTableViewSection { + let visibleSections = sections.compactMap({ $0.isHidden ? nil : $0 }) + + return visibleSections[indexPath.section] + } + +} diff --git a/ios/MullvadVPN/TranslucentButtonBlurView.swift b/ios/MullvadVPN/TranslucentButtonBlurView.swift index ea835497c0..b4bc874f1e 100644 --- a/ios/MullvadVPN/TranslucentButtonBlurView.swift +++ b/ios/MullvadVPN/TranslucentButtonBlurView.swift @@ -1,5 +1,5 @@ // -// AppButton.swift +// TranslucentButtonBlurView.swift // MullvadVPN // // Created by pronebird on 20/03/2019. diff --git a/ios/MullvadVPN/UIColor+Palette.swift b/ios/MullvadVPN/UIColor+Palette.swift index 425b9fb961..72a1b4ace8 100644 --- a/ios/MullvadVPN/UIColor+Palette.swift +++ b/ios/MullvadVPN/UIColor+Palette.swift @@ -18,8 +18,8 @@ extension UIColor { } struct ErrorState { - static let borderColor = UIColor(red: 0.82, green: 0.01, blue: 0.11, alpha: 0.4) - static let textColor = UIColor(red: 0.82, green: 0.01, blue: 0.11, alpha: 1.0) + static let borderColor = dangerColor.withAlphaComponent(0.4) + static let textColor = dangerColor static let backgroundColor = UIColor.white } @@ -32,15 +32,26 @@ extension UIColor { // Relay availability indicator view struct RelayStatusIndicator { - static let activeColor = UIColor(red: 0.27, green: 0.68, blue: 0.30, alpha: 0.9) - static let inactiveColor = UIColor(red: 0.82, green: 0.01, blue: 0.11, alpha: 0.95) + static let activeColor = successColor.withAlphaComponent(0.9) + static let inactiveColor = dangerColor.withAlphaComponent(0.95) } // Cells struct Cell { static let backgroundColor = UIColor(red: 0.16, green: 0.30, blue: 0.45, alpha: 1.0) - static let selectedBackgroundColor = UIColor(red: 0.27, green: 0.68, blue: 0.30, alpha: 1.0) - static let subCellBackgroundColor = UIColor(red:0.15, green:0.23, blue:0.33, alpha:1.0) - static let subSubCellBackgroundColor = UIColor(red:0.13, green:0.20, blue:0.30, alpha:1.0) + static let selectedAltBackgroundColor = backgroundColor.darkened(by: 0.2) + static let selectedBackgroundColor = successColor + static let subCellBackgroundColor = UIColor(red: 0.15, green: 0.23, blue: 0.33, alpha: 1.0) + static let subSubCellBackgroundColor = UIColor(red: 0.13, green: 0.20, blue: 0.30, alpha: 1.0) } + + struct HeaderBar { + static let defaultBackgroundColor = UIColor(red: 0.16, green: 0.30, blue: 0.45, alpha: 1.0) + static let unsecuredBackgroundColor = dangerColor + static let securedBackgroundColor = successColor + } + + // Common colors + static let dangerColor = UIColor(red: 0.82, green: 0.01, blue: 0.11, alpha: 1.0) + static let successColor = UIColor(red: 0.27, green: 0.68, blue: 0.30, alpha: 1.0) } diff --git a/ios/MullvadVPN/UserDefaultsInteractor.swift b/ios/MullvadVPN/UserDefaultsInteractor.swift index 7156e96381..786611e448 100644 --- a/ios/MullvadVPN/UserDefaultsInteractor.swift +++ b/ios/MullvadVPN/UserDefaultsInteractor.swift @@ -21,13 +21,13 @@ private enum UserDefaultsKeys: String { class UserDefaultsInteractor { let userDefaults: UserDefaults - /// Returns the instance of UserDefaultsInteractor initialized with the application preferences - /// scoped to the application group. - class func withApplicationGroupUserDefaults() -> UserDefaultsInteractor { + /// The shared instance of UserDefaultsInteractor initialized with the application group + /// preferences + static let sharedApplicationGroupInteractor: UserDefaultsInteractor = { let userDefaults = UserDefaults(suiteName: kApplicationGroupIdentifier)! return UserDefaultsInteractor(userDefaults: userDefaults) - } + }() init(userDefaults: UserDefaults) { self.userDefaults = userDefaults diff --git a/ios/MullvadVPN/ViewControllerIdentifier.swift b/ios/MullvadVPN/ViewControllerIdentifier.swift new file mode 100644 index 0000000000..a81f39320b --- /dev/null +++ b/ios/MullvadVPN/ViewControllerIdentifier.swift @@ -0,0 +1,14 @@ +// +// ViewControllerIdentifier.swift +// MullvadVPN +// +// Created by pronebird on 23/05/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import Foundation + +enum ViewControllerIdentifier: String { + case login = "Login" + case main = "Main" +} diff --git a/ios/convert-assets.rb b/ios/convert-assets.rb index f0b4c752e6..dd419bcbcb 100755 --- a/ios/convert-assets.rb +++ b/ios/convert-assets.rb @@ -74,6 +74,7 @@ APP_ICON_SIZES=[ ADDITIONAL_ASSETS = [ "DefaultButton.svg", "SuccessButton.svg", + "DangerButton.svg", "TranslucentDangerButton.svg", "TranslucentNeutralButton.svg" ] |
