summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2019-05-28 12:09:55 +0200
committerAndrej Mihajlov <and@mullvad.net>2019-05-28 12:09:55 +0200
commit5da250dc85c96f3f83b6b1c1d490e6afb5f2db53 (patch)
tree8b71a657048c84257c54af56fde600545b0751bb
parent1874d48758875482be85005bc3b7734bef4e2d35 (diff)
parent1bfe1454b8149d76978d8118700910c6ee4faab7 (diff)
downloadmullvadvpn-5da250dc85c96f3f83b6b1c1d490e6afb5f2db53.tar.xz
mullvadvpn-5da250dc85c96f3f83b6b1c1d490e6afb5f2db53.zip
Merge branch 'account-view'
-rw-r--r--ios/AdditionalAssets/DangerButton.svg3
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj44
-rw-r--r--ios/MullvadVPN/Account.swift18
-rw-r--r--ios/MullvadVPN/AccountExpiry.swift39
-rw-r--r--ios/MullvadVPN/AccountViewController.swift35
-rw-r--r--ios/MullvadVPN/AppDelegate.swift17
-rw-r--r--ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/Contents.json26
-rw-r--r--ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/DangerButton.pdfbin0 -> 999 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/DefaultButton.imageset/DefaultButton.pdfbin998 -> 998 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconExtlink.imageset/Contents.json3
-rw-r--r--ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/SuccessButton.pdfbin999 -> 999 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/TranslucentDangerButton.imageset/TranslucentDangerButton.pdfbin1003 -> 1003 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/TranslucentNeutralButton.imageset/TranslucentNeutralButton.pdfbin1002 -> 1002 bytes
-rw-r--r--ios/MullvadVPN/Base.lproj/Main.storyboard478
-rw-r--r--ios/MullvadVPN/BasicTableViewCell.swift1
-rw-r--r--ios/MullvadVPN/ConnectViewController.swift15
-rw-r--r--ios/MullvadVPN/CustomButton.swift65
-rw-r--r--ios/MullvadVPN/CustomNavigationBar.swift77
-rw-r--r--ios/MullvadVPN/HeaderBarViewController.swift23
-rw-r--r--ios/MullvadVPN/LoginViewController.swift31
-rw-r--r--ios/MullvadVPN/RootContainerViewController.swift346
-rw-r--r--ios/MullvadVPN/SegueIdentifier.swift9
-rw-r--r--ios/MullvadVPN/SelectLocationCell.swift9
-rw-r--r--ios/MullvadVPN/SelectLocationController.swift45
-rw-r--r--ios/MullvadVPN/SettingsAccountCell.swift50
-rw-r--r--ios/MullvadVPN/SettingsAppVersionCell.swift13
-rw-r--r--ios/MullvadVPN/SettingsCell.swift24
-rw-r--r--ios/MullvadVPN/SettingsViewController.swift65
-rw-r--r--ios/MullvadVPN/StaticTableViewDataSource.swift92
-rw-r--r--ios/MullvadVPN/TranslucentButtonBlurView.swift2
-rw-r--r--ios/MullvadVPN/UIColor+Palette.swift25
-rw-r--r--ios/MullvadVPN/UserDefaultsInteractor.swift8
-rw-r--r--ios/MullvadVPN/ViewControllerIdentifier.swift14
-rwxr-xr-xios/convert-assets.rb1
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
new file mode 100644
index 0000000000..c1f95daf3b
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/DangerButton.pdf
Binary files differ
diff --git a/ios/MullvadVPN/Assets.xcassets/DefaultButton.imageset/DefaultButton.pdf b/ios/MullvadVPN/Assets.xcassets/DefaultButton.imageset/DefaultButton.pdf
index ab05d02bb7..efae69e9a3 100644
--- a/ios/MullvadVPN/Assets.xcassets/DefaultButton.imageset/DefaultButton.pdf
+++ b/ios/MullvadVPN/Assets.xcassets/DefaultButton.imageset/DefaultButton.pdf
Binary files differ
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
index 3d9f601cd0..fb318000c5 100644
--- a/ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/SuccessButton.pdf
+++ b/ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/SuccessButton.pdf
Binary files differ
diff --git a/ios/MullvadVPN/Assets.xcassets/TranslucentDangerButton.imageset/TranslucentDangerButton.pdf b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerButton.imageset/TranslucentDangerButton.pdf
index a69b22fb31..dc8dc8d705 100644
--- a/ios/MullvadVPN/Assets.xcassets/TranslucentDangerButton.imageset/TranslucentDangerButton.pdf
+++ b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerButton.imageset/TranslucentDangerButton.pdf
Binary files differ
diff --git a/ios/MullvadVPN/Assets.xcassets/TranslucentNeutralButton.imageset/TranslucentNeutralButton.pdf b/ios/MullvadVPN/Assets.xcassets/TranslucentNeutralButton.imageset/TranslucentNeutralButton.pdf
index 82ee77fd9a..b4f5b3b123 100644
--- a/ios/MullvadVPN/Assets.xcassets/TranslucentNeutralButton.imageset/TranslucentNeutralButton.pdf
+++ b/ios/MullvadVPN/Assets.xcassets/TranslucentNeutralButton.imageset/TranslucentNeutralButton.pdf
Binary files differ
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"
]