summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2020-07-30 10:10:17 +0300
committerAndrej Mihajlov <and@mullvad.net>2020-07-30 10:10:17 +0300
commit43110ce2e27fcda977458469005921bec788a1de (patch)
tree1c976d19b58e9a6d57e4673bfd9e1fa76ac0bd98
parentca590bb989cea195b67208dc49da9419da14e9a1 (diff)
parent437dcd524b8285735c9de3f3ce616d10614adc85 (diff)
downloadmullvadvpn-43110ce2e27fcda977458469005921bec788a1de.tar.xz
mullvadvpn-43110ce2e27fcda977458469005921bec788a1de.zip
Merge branch 'add-reconnect-button'
-rw-r--r--ios/AdditionalAssets/TranslucentDangerSplitLeftButton.svg3
-rw-r--r--ios/AdditionalAssets/TranslucentDangerSplitRightButton.svg3
-rw-r--r--ios/CHANGELOG.md1
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj16
-rw-r--r--ios/MullvadVPN/AppButton.swift65
-rw-r--r--ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/DangerButton.pdfbin999 -> 994 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/DefaultButton.imageset/DefaultButton.pdfbin998 -> 994 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconReload.imageset/Contents.json16
-rw-r--r--ios/MullvadVPN/Assets.xcassets/IconReload.imageset/IconReload.pdf71
-rw-r--r--ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/Contents.json26
-rw-r--r--ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/SuccessButton.pdfbin999 -> 995 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/TranslucentDangerButton.imageset/TranslucentDangerButton.pdfbin1003 -> 998 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitLeftButton.imageset/Contents.json26
-rw-r--r--ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitLeftButton.imageset/TranslucentDangerSplitLeftButton.pdfbin0 -> 980 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitRightButton.imageset/Contents.json26
-rw-r--r--ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitRightButton.imageset/TranslucentDangerSplitRightButton.pdfbin0 -> 980 bytes
-rw-r--r--ios/MullvadVPN/Assets.xcassets/TranslucentNeutralButton.imageset/TranslucentNeutralButton.pdfbin1002 -> 998 bytes
-rw-r--r--ios/MullvadVPN/Base.lproj/Main.storyboard273
-rw-r--r--ios/MullvadVPN/Bundle+MullvadVersion.swift24
-rw-r--r--ios/MullvadVPN/ConnectViewController.swift120
-rw-r--r--ios/MullvadVPN/ConnectionPanelView.swift6
-rw-r--r--ios/MullvadVPN/DisconnectSplitButton.swift59
-rw-r--r--ios/MullvadVPN/DisconnectSplitButton.xib101
-rw-r--r--ios/MullvadVPN/LoginViewController.swift4
-rw-r--r--ios/MullvadVPN/SegueIdentifier.swift4
-rw-r--r--ios/MullvadVPN/SelectLocationCell.swift6
-rw-r--r--ios/MullvadVPN/SettingsViewController.swift3
-rw-r--r--ios/MullvadVPN/TranslucentButtonBlurView.swift28
-rw-r--r--ios/MullvadVPN/TunnelControlViewController.swift104
-rw-r--r--ios/MullvadVPN/TunnelManager.swift22
-rwxr-xr-xios/convert-assets.rb10
31 files changed, 573 insertions, 444 deletions
diff --git a/ios/AdditionalAssets/TranslucentDangerSplitLeftButton.svg b/ios/AdditionalAssets/TranslucentDangerSplitLeftButton.svg
new file mode 100644
index 0000000000..ef4210291e
--- /dev/null
+++ b/ios/AdditionalAssets/TranslucentDangerSplitLeftButton.svg
@@ -0,0 +1,3 @@
+<svg viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg">
+ <path d="M4 0 L44 0 Q44 0 44 0 L44 44 Q44 44 44 44 L4 44 Q0 44 0 40 L0 4 Q0 0 4 0 Z" fill="rgba(227, 64, 57, 0.40)"></path>
+</svg>
diff --git a/ios/AdditionalAssets/TranslucentDangerSplitRightButton.svg b/ios/AdditionalAssets/TranslucentDangerSplitRightButton.svg
new file mode 100644
index 0000000000..12e8e1442e
--- /dev/null
+++ b/ios/AdditionalAssets/TranslucentDangerSplitRightButton.svg
@@ -0,0 +1,3 @@
+<svg viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg">
+ <path d="M0 0 L40 0 Q44 0 44 4 L44 40 Q44 44 40 44 L0 44 Q0 44 0 44 L0 0 Q0 0 0 0 Z" fill="rgba(227, 64, 57, 0.40)"></path>
+</svg>
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index fccf62bda3..13cb39d70b 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -24,6 +24,7 @@ Line wrap the file at 100 chars. Th
## [Unreleased]
### Added
+- Add button to reconnect the tunnel.
- Add support for iOS 12.
- Ship the initial relay list with the app, and do once an hour periodic refresh in background.
- Refresh account expiry when visiting settings.
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index dc01442a6d..0c188fa520 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -54,7 +54,6 @@
5840250222B1124600E4CFEC /* IpAddress+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5840250022B1124600E4CFEC /* IpAddress+Codable.swift */; };
5840250422B11AB700E4CFEC /* MullvadEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */; };
5840250522B11AB700E4CFEC /* MullvadEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */; };
- 5845F838236C466400B2D93C /* TunnelControlViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5845F837236C466400B2D93C /* TunnelControlViewController.swift */; };
5845F842236CBACD00B2D93C /* PacketTunnelIpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5845F841236CBACD00B2D93C /* PacketTunnelIpc.swift */; };
5845F843236CBDAB00B2D93C /* PacketTunnelIpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5845F841236CBACD00B2D93C /* PacketTunnelIpc.swift */; };
584E96BC240FD4DA00D3334F /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8623F43901009F7EA6 /* Location.swift */; };
@@ -106,6 +105,8 @@
5888AD87227B17950051EB06 /* SelectLocationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5888AD86227B17950051EB06 /* SelectLocationController.swift */; };
588D2FE3248AC27F00E313F7 /* AsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E973DD24850EB600096F90 /* AsyncOperation.swift */; };
58906DE02445C7A5002F0673 /* NEProviderStopReason+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */; };
+ 58907D9324D167B400CFC3F5 /* DisconnectSplitButton.xib in Resources */ = {isa = PBXBuildFile; fileRef = 58907D9224D167B400CFC3F5 /* DisconnectSplitButton.xib */; };
+ 58907D9524D17B4E00CFC3F5 /* DisconnectSplitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */; };
5896AE7E246ACE65005B36CB /* KeychainAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FAEDEB245059F000CB0F5B /* KeychainAttributes.swift */; };
5896AE7F246ACE76005B36CB /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FAEDF6245088E100CB0F5B /* Keychain.swift */; };
5896AE80246ACE79005B36CB /* KeychainClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FAEE0024533A9C00CB0F5B /* KeychainClass.swift */; };
@@ -169,7 +170,6 @@
58D0C7A223F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D0C7A023F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift */; };
58DF28A52417CB4B00E836B0 /* AppStorePaymentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF28A42417CB4B00E836B0 /* AppStorePaymentManager.swift */; };
58E6771F24ADFE7800AA26E7 /* SettingsNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E6771E24ADFE7800AA26E7 /* SettingsNavigationController.swift */; };
- 58EC4E6C23915325003F5C5B /* Bundle+MullvadVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EC4E6B23915325003F5C5B /* Bundle+MullvadVersion.swift */; };
58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */; };
58F3C0962492617E003E76BE /* AsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E973DD24850EB600096F90 /* AsyncOperation.swift */; };
58F3C099249B978C003E76BE /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 58F3C098249B978C003E76BE /* x25519.c */; };
@@ -270,7 +270,6 @@
5835B7CB233B76CB0096D79F /* TunnelManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManager.swift; sourceTree = "<group>"; };
5840250022B1124600E4CFEC /* IpAddress+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IpAddress+Codable.swift"; sourceTree = "<group>"; };
5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadEndpoint.swift; sourceTree = "<group>"; };
- 5845F837236C466400B2D93C /* TunnelControlViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelControlViewController.swift; sourceTree = "<group>"; };
5845F841236CBACD00B2D93C /* PacketTunnelIpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelIpc.swift; sourceTree = "<group>"; };
584B26F3237434D00073B10E /* RelaySelectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorTests.swift; sourceTree = "<group>"; };
58561C98239A5D1500BD6B5E /* IPEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPEndpoint.swift; sourceTree = "<group>"; };
@@ -300,6 +299,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>"; };
58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEProviderStopReason+Debug.swift"; sourceTree = "<group>"; };
+ 58907D9224D167B400CFC3F5 /* DisconnectSplitButton.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DisconnectSplitButton.xib; sourceTree = "<group>"; };
+ 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisconnectSplitButton.swift; sourceTree = "<group>"; };
5894E725236B2801008A2793 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDateComponentsFormatting.swift; sourceTree = "<group>"; };
5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDateComponentsFormattingTests.swift; sourceTree = "<group>"; };
@@ -351,7 +352,6 @@
58DF28A42417CB4B00E836B0 /* AppStorePaymentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorePaymentManager.swift; sourceTree = "<group>"; };
58E6771E24ADFE7800AA26E7 /* SettingsNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsNavigationController.swift; sourceTree = "<group>"; };
58E973DD24850EB600096F90 /* AsyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncOperation.swift; sourceTree = "<group>"; };
- 58EC4E6B23915325003F5C5B /* Bundle+MullvadVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+MullvadVersion.swift"; sourceTree = "<group>"; };
58ECD29123F178FD004298B6 /* Screenshots.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Screenshots.xcconfig; sourceTree = "<group>"; };
58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerActivityIndicatorView.swift; sourceTree = "<group>"; };
58F3C097249B978C003E76BE /* x25519.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = x25519.h; sourceTree = "<group>"; };
@@ -498,7 +498,6 @@
58CE5E6A224146210008646E /* Assets.xcassets */,
588534BD246193C00018B744 /* AutomaticKeyRotationManager.swift */,
589AB4F6227B64450039131E /* BasicTableViewCell.swift */,
- 58EC4E6B23915325003F5C5B /* Bundle+MullvadVersion.swift */,
58F840B12464491D0044E708 /* ChainedError.swift */,
58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */,
58CCA00F224249A1004F3011 /* ConnectViewController.swift */,
@@ -507,6 +506,8 @@
5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */,
582BB1B0229569620055B6EF /* CustomNavigationBar.swift */,
58C6B35D22BBBFE3003C19AD /* Data+HexCoding.swift */,
+ 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */,
+ 58907D9224D167B400CFC3F5 /* DisconnectSplitButton.xib */,
58B9EB142489139B00095626 /* DisplayChainedError.swift */,
5873884C239E6D7E00E96C4E /* EmbeddedViewContainerView.swift */,
58F3C0A3249CB069003E76BE /* HeaderBarView.swift */,
@@ -560,7 +561,6 @@
581CBCED229826FD00727D7F /* StaticTableViewDataSource.swift */,
5807E2BF2432038B00F5FF30 /* String+Split.swift */,
5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */,
- 5845F837236C466400B2D93C /* TunnelControlViewController.swift */,
5835B7CB233B76CB0096D79F /* TunnelManager.swift */,
587AD7C523421D7000E93A53 /* TunnelSettings.swift */,
58AEEF6A2344A46200C9BBD5 /* TunnelSettingsManager.swift */,
@@ -798,6 +798,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 58907D9324D167B400CFC3F5 /* DisconnectSplitButton.xib in Resources */,
58F3C0A2249CA1E0003E76BE /* HeaderBarView.xib in Resources */,
58F3C0A624A50157003E76BE /* relays.json in Resources */,
58CE5E6E224146210008646E /* LaunchScreen.storyboard in Resources */,
@@ -912,7 +913,6 @@
58BA692E23E99EFF009DC256 /* Locking.swift in Sources */,
580EE21E24B3237F00F9D8A1 /* OutputOperation.swift in Sources */,
5840250122B1124600E4CFEC /* IpAddress+Codable.swift in Sources */,
- 58EC4E6C23915325003F5C5B /* Bundle+MullvadVersion.swift in Sources */,
5857F24724C882D700CF6F47 /* SelectLocationNavigationController.swift in Sources */,
580EE21224B322FC00F9D8A1 /* ResultOperation.swift in Sources */,
58BA693123EADA6A009DC256 /* SimulatorTunnelProvider.swift in Sources */,
@@ -933,12 +933,12 @@
580EE20924B3224200F9D8A1 /* RetryOperation.swift in Sources */,
582AE3102440A6CA00E6733A /* AccountTokenInput.swift in Sources */,
58FAEDF7245088E100CB0F5B /* Keychain.swift in Sources */,
+ 58907D9524D17B4E00CFC3F5 /* DisconnectSplitButton.swift in Sources */,
5888AD87227B17950051EB06 /* SelectLocationController.swift in Sources */,
580EE20424B321EC00F9D8A1 /* OperationObserver.swift in Sources */,
58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */,
58A99ED3240014A0006599E9 /* ConsentViewController.swift in Sources */,
58FAEE0124533A9C00CB0F5B /* KeychainClass.swift in Sources */,
- 5845F838236C466400B2D93C /* TunnelControlViewController.swift in Sources */,
58CCA0162242560B004F3011 /* UIColor+Palette.swift in Sources */,
58AEEF6B2344A46200C9BBD5 /* TunnelSettingsManager.swift in Sources */,
587CBFE322807F530028DED3 /* UIColor+Helpers.swift in Sources */,
diff --git a/ios/MullvadVPN/AppButton.swift b/ios/MullvadVPN/AppButton.swift
index 25eed30cf8..6a58100c25 100644
--- a/ios/MullvadVPN/AppButton.swift
+++ b/ios/MullvadVPN/AppButton.swift
@@ -98,6 +98,54 @@ private extension UIControl.State {
/// A subclass that implements action buttons used across the app
@IBDesignable class AppButton: CustomButton {
+ static let defaultContentInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
+
+ enum Style: Int {
+ case `default`
+ case danger
+ case success
+ case translucentDanger
+ case translucentNeutral
+ case translucentDangerSplitLeft
+ case translucentDangerSplitRight
+
+ var backgroundImage: UIImage? {
+ switch self {
+ case .default:
+ return UIImage(named: "DefaultButton")
+ case .danger:
+ return UIImage(named: "DangerButton")
+ case .success:
+ return UIImage(named: "SuccessButton")
+ case .translucentDanger:
+ return UIImage(named: "TranslucentDangerButton")
+ case .translucentNeutral:
+ return UIImage(named: "TranslucentNeutralButton")
+ case .translucentDangerSplitLeft:
+ return UIImage(named: "TranslucentDangerSplitLeftButton")
+ case .translucentDangerSplitRight:
+ return UIImage(named: "TranslucentDangerSplitRightButton")
+ }
+ }
+ }
+
+ var style: Style = .default {
+ didSet {
+ updateButtonBackground()
+ }
+ }
+
+ @IBInspectable var interfaceBuilderStyle: Int {
+ get {
+ return self.style.rawValue
+ }
+ set {
+ if let style = Style(rawValue: newValue) {
+ self.style = style
+ }
+ }
+ }
+
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
@@ -112,19 +160,19 @@ private extension UIControl.State {
var contentInsets = contentEdgeInsets
if contentInsets.top == 0 {
- contentInsets.top = 10
+ contentInsets.top = Self.defaultContentInsets.top
}
if contentInsets.bottom == 0 {
- contentInsets.bottom = 10
+ contentInsets.bottom = Self.defaultContentInsets.bottom
}
if contentInsets.right == 0 {
- contentInsets.right = 10
+ contentInsets.right = Self.defaultContentInsets.right
}
if contentInsets.left == 0 {
- contentInsets.left = 10
+ contentInsets.left = Self.defaultContentInsets.left
}
contentEdgeInsets = contentInsets
@@ -138,6 +186,15 @@ private extension UIControl.State {
setTitleColor(titleColor, for: state)
}
}
+
+ // Avoid setting the background image if it's already set via Interface Builder
+ if backgroundImage(for: .normal) == nil {
+ updateButtonBackground()
+ }
+ }
+
+ private func updateButtonBackground() {
+ setBackgroundImage(style.backgroundImage, for: .normal)
}
}
diff --git a/ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/DangerButton.pdf b/ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/DangerButton.pdf
index c1f95daf3b..7350298c29 100644
--- a/ios/MullvadVPN/Assets.xcassets/DangerButton.imageset/DangerButton.pdf
+++ 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 efae69e9a3..0bf94571f6 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/IconReload.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/IconReload.imageset/Contents.json
new file mode 100644
index 0000000000..ff6e723432
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/IconReload.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "IconReload.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/ios/MullvadVPN/Assets.xcassets/IconReload.imageset/IconReload.pdf b/ios/MullvadVPN/Assets.xcassets/IconReload.imageset/IconReload.pdf
new file mode 100644
index 0000000000..dd17312eec
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/IconReload.imageset/IconReload.pdf
@@ -0,0 +1,71 @@
+%PDF-1.5
+%
+4 0 obj
+<< /Length 5 0 R
+ /Filter /FlateDecode
+>>
+stream
+xURKNE1wl@,p %9PxS(pI Q}1_' FEd3$Y)W kJ.^t#_S|rt'OdzW(t}.H܆"atk~kmۖyXMk7BqH)pjgH
+lBRT,hseXJ֙%P/y#\ͯ YR[<<4Ɓ(d>n3<q5M!V=)7Za
+h0ȵp? ~p{80
+i)M kdq!wma*lE!uqI A
+endstream
+endobj
+5 0 obj
+ 360
+endobj
+3 0 obj
+<<
+ /ExtGState <<
+ /a0 << /CA 1 /ca 1 >>
+ >>
+>>
+endobj
+2 0 obj
+<< /Type /Page % 1
+ /Parent 1 0 R
+ /MediaBox [ 0 0 512 512 ]
+ /Contents 4 0 R
+ /Group <<
+ /Type /Group
+ /S /Transparency
+ /I true
+ /CS /DeviceRGB
+ >>
+ /Resources 3 0 R
+>>
+endobj
+1 0 obj
+<< /Type /Pages
+ /Kids [ 2 0 R ]
+ /Count 1
+>>
+endobj
+6 0 obj
+<< /Producer (cairo 1.16.0 (https://cairographics.org))
+ /CreationDate (D:20200723135151+03'00)
+>>
+endobj
+7 0 obj
+<< /Type /Catalog
+ /Pages 1 0 R
+>>
+endobj
+xref
+0 8
+0000000000 65535 f
+0000000764 00000 n
+0000000546 00000 n
+0000000474 00000 n
+0000000015 00000 n
+0000000452 00000 n
+0000000829 00000 n
+0000000945 00000 n
+trailer
+<< /Size 8
+ /Root 7 0 R
+ /Info 6 0 R
+>>
+startxref
+997
+%%EOF
diff --git a/ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/Contents.json
index 37fe41d860..1c5bb6e4fc 100644
--- a/ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/Contents.json
+++ b/ios/MullvadVPN/Assets.xcassets/SuccessButton.imageset/Contents.json
@@ -1,26 +1,26 @@
{
"images" : [
{
- "idiom" : "universal",
"filename" : "SuccessButton.pdf",
+ "idiom" : "universal",
"resizing" : {
- "mode" : "9-part",
- "center" : {
- "mode" : "tile",
- "width" : 1,
- "height" : 1
- },
"cap-insets" : {
"bottom" : 4,
- "top" : 4,
+ "left" : 4,
"right" : 4,
- "left" : 4
- }
+ "top" : 4
+ },
+ "center" : {
+ "height" : 1,
+ "mode" : "tile",
+ "width" : 1
+ },
+ "mode" : "9-part"
}
}
],
"info" : {
- "version" : 1,
- "author" : "xcode"
+ "author" : "xcode",
+ "version" : 1
}
-} \ 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 fb318000c5..005ed8e333 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 dc8dc8d705..dd63aaa23c 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/TranslucentDangerSplitLeftButton.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitLeftButton.imageset/Contents.json
new file mode 100644
index 0000000000..3ffcbb4089
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitLeftButton.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "TranslucentDangerSplitLeftButton.pdf",
+ "idiom" : "universal",
+ "resizing" : {
+ "cap-insets" : {
+ "bottom" : 3,
+ "left" : 21,
+ "right" : 22,
+ "top" : 3
+ },
+ "center" : {
+ "height" : 1,
+ "mode" : "tile",
+ "width" : 1
+ },
+ "mode" : "9-part"
+ }
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitLeftButton.imageset/TranslucentDangerSplitLeftButton.pdf b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitLeftButton.imageset/TranslucentDangerSplitLeftButton.pdf
new file mode 100644
index 0000000000..8a9ca176c1
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitLeftButton.imageset/TranslucentDangerSplitLeftButton.pdf
Binary files differ
diff --git a/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitRightButton.imageset/Contents.json b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitRightButton.imageset/Contents.json
new file mode 100644
index 0000000000..560e715aae
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitRightButton.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "TranslucentDangerSplitRightButton.pdf",
+ "idiom" : "universal",
+ "resizing" : {
+ "cap-insets" : {
+ "bottom" : 3,
+ "left" : 21,
+ "right" : 22,
+ "top" : 3
+ },
+ "center" : {
+ "height" : 1,
+ "mode" : "tile",
+ "width" : 1
+ },
+ "mode" : "9-part"
+ }
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitRightButton.imageset/TranslucentDangerSplitRightButton.pdf b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitRightButton.imageset/TranslucentDangerSplitRightButton.pdf
new file mode 100644
index 0000000000..f033dc42f6
--- /dev/null
+++ b/ios/MullvadVPN/Assets.xcassets/TranslucentDangerSplitRightButton.imageset/TranslucentDangerSplitRightButton.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 b4f5b3b123..fc29439ce1 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 9cf7ac24e6..7bb8bab957 100644
--- a/ios/MullvadVPN/Base.lproj/Main.storyboard
+++ b/ios/MullvadVPN/Base.lproj/Main.storyboard
@@ -206,7 +206,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="3rI-k6-N1S">
- <rect key="frame" x="24" y="245" width="327" height="212"/>
+ <rect key="frame" x="24" y="307" width="327" height="212"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SECURE CONNECTION" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HNy-mU-nui">
<rect key="frame" x="0.0" y="0.0" width="327" height="24"/>
@@ -253,29 +253,39 @@
</view>
</subviews>
</stackView>
- <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Dv1-XA-XQG" userLabel="Tunnel control buttons">
- <rect key="frame" x="24" y="481" width="327" height="162"/>
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kbg-vd-uS0">
+ <rect key="frame" x="24" y="543" width="327" height="100"/>
+ <subviews>
+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="oZQ-uX-qg5">
+ <rect key="frame" x="0.0" y="0.0" width="327" height="100"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="100" placeholder="YES" id="xyU-Ve-38D"/>
+ </constraints>
+ </stackView>
+ </subviews>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
- <constraint firstAttribute="height" constant="162" placeholder="YES" id="kLh-W4-WzX"/>
+ <constraint firstItem="oZQ-uX-qg5" firstAttribute="leading" secondItem="kbg-vd-uS0" secondAttribute="leading" id="CVK-s8-K47"/>
+ <constraint firstAttribute="trailing" secondItem="oZQ-uX-qg5" secondAttribute="trailing" id="IEd-jf-HBD"/>
+ <constraint firstAttribute="bottom" secondItem="oZQ-uX-qg5" secondAttribute="bottom" id="pAj-Dl-cfL"/>
+ <constraint firstItem="oZQ-uX-qg5" firstAttribute="top" secondItem="kbg-vd-uS0" secondAttribute="top" id="qfE-M4-Vqw"/>
</constraints>
- <connections>
- <segue destination="7We-1v-DIF" kind="embed" identifier="EmbedTunnelControls" id="SUp-a7-wNJ"/>
- </connections>
- </containerView>
+ </view>
</subviews>
<color key="backgroundColor" name="Primary"/>
<constraints>
<constraint firstItem="3rI-k6-N1S" firstAttribute="leading" secondItem="PNd-mm-N1B" secondAttribute="leadingMargin" id="5fs-z1-QKf"/>
<constraint firstAttribute="trailingMargin" secondItem="3rI-k6-N1S" secondAttribute="trailing" id="NFr-rI-7r3"/>
- <constraint firstItem="iBo-pG-OTz" firstAttribute="trailing" secondItem="Dv1-XA-XQG" secondAttribute="trailing" constant="24" id="OPf-p4-dPn"/>
- <constraint firstItem="Dv1-XA-XQG" firstAttribute="top" secondItem="3rI-k6-N1S" secondAttribute="bottom" constant="24" id="hRj-EJ-ls3"/>
- <constraint firstItem="iBo-pG-OTz" firstAttribute="bottom" secondItem="Dv1-XA-XQG" secondAttribute="bottom" constant="24" id="sjJ-M6-O74"/>
- <constraint firstItem="Dv1-XA-XQG" firstAttribute="leading" secondItem="iBo-pG-OTz" secondAttribute="leading" constant="24" id="ye6-tj-a0l"/>
+ <constraint firstItem="iBo-pG-OTz" firstAttribute="bottom" secondItem="kbg-vd-uS0" secondAttribute="bottom" constant="24" id="Und-F7-4LQ"/>
+ <constraint firstItem="kbg-vd-uS0" firstAttribute="leading" secondItem="iBo-pG-OTz" secondAttribute="leading" constant="24" id="WON-cN-WSC"/>
+ <constraint firstItem="kbg-vd-uS0" firstAttribute="top" secondItem="3rI-k6-N1S" secondAttribute="bottom" constant="24" id="Yrd-dM-tsp"/>
+ <constraint firstItem="iBo-pG-OTz" firstAttribute="trailing" secondItem="kbg-vd-uS0" secondAttribute="trailing" constant="24" id="cbX-QT-hOe"/>
</constraints>
<edgeInsets key="layoutMargins" top="0.0" left="24" bottom="24" right="24"/>
<viewLayoutGuide key="safeArea" id="iBo-pG-OTz"/>
</view>
<connections>
+ <outlet property="buttonsStackView" destination="oZQ-uX-qg5" id="dvm-AG-6HG"/>
<outlet property="cityLabel" destination="mfr-Ic-PYf" id="vut-Me-cdj"/>
<outlet property="connectionPanel" destination="ocV-9f-WDZ" id="Uad-bl-KFU"/>
<outlet property="countryLabel" destination="9Yf-sl-l3q" id="L3N-Jn-zlr"/>
@@ -895,7 +905,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JYh-33-d0O">
- <rect key="frame" x="0.0" y="0.0" width="375" height="597"/>
+ <rect key="frame" x="0.0" y="0.0" width="375" height="598"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="N9k-cQ-tlw" userLabel="Content view">
<rect key="frame" x="0.0" y="0.0" width="375" height="558"/>
@@ -934,7 +944,7 @@
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Cas-Tk-gcz" customClass="LinkButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="20" y="516" width="128" height="22"/>
+ <rect key="frame" x="20" y="516" width="20" height="22"/>
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="18"/>
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" title="Privacy Policy" image="IconExtlink"/>
@@ -970,10 +980,10 @@
</constraints>
</scrollView>
<view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="16P-Q0-ZO9" userLabel="Footer">
- <rect key="frame" x="0.0" y="597" width="375" height="70"/>
+ <rect key="frame" x="0.0" y="598" width="375" height="69"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ttw-7B-1MM" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="16" y="24" width="343" height="22"/>
+ <rect key="frame" x="16" y="24" width="343" height="21"/>
<accessibility key="accessibilityConfiguration" identifier="AgreeButton"/>
<state key="normal" title="Agree and continue" backgroundImage="DefaultButton">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -1011,235 +1021,6 @@
</objects>
<point key="canvasLocation" x="-551" y="27"/>
</scene>
- <!--Tunnel Control View Controller-->
- <scene sceneID="Zzn-S1-fVu">
- <objects>
- <viewController id="7We-1v-DIF" customClass="TunnelControlViewController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController">
- <view key="view" contentMode="scaleToFill" id="Ayf-ZB-cCJ">
- <rect key="frame" x="0.0" y="0.0" width="327" height="162"/>
- <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
- <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <viewLayoutGuide key="safeArea" id="MPh-Rn-8Ad"/>
- </view>
- <connections>
- <outlet property="connectedView" destination="KX2-Pi-Myg" id="qSH-w0-rkV"/>
- <outlet property="connectingView" destination="ZYQ-G2-MXc" id="Hg7-Sg-PFl"/>
- <outlet property="disconnectedView" destination="7l6-on-fbR" id="UT0-9I-faY"/>
- </connections>
- </viewController>
- <placeholder placeholderIdentifier="IBFirstResponder" id="C3V-lo-QFw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
- <view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="7l6-on-fbR">
- <rect key="frame" x="0.0" y="0.0" width="261" height="121"/>
- <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
- <subviews>
- <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="shY-Lj-oYx">
- <rect key="frame" x="0.0" y="0.0" width="261" height="121"/>
- <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="261" height="52.5"/>
- <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="261" height="52.5"/>
- <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
- <subviews>
- <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hVz-q0-Xpd" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52.5"/>
- <accessibility key="accessibilityConfiguration" identifier="SelectLocationButton"/>
- <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>
- <connections>
- <action selector="handleSelectLocation:" destination="7We-1v-DIF" eventType="touchUpInside" id="P4j-WA-6kP"/>
- </connections>
- </button>
- </subviews>
- <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <constraints>
- <constraint firstAttribute="bottom" secondItem="hVz-q0-Xpd" secondAttribute="bottom" id="4Q2-Zh-7fM"/>
- <constraint firstAttribute="trailing" secondItem="hVz-q0-Xpd" secondAttribute="trailing" id="AsY-cg-1fj"/>
- <constraint firstItem="hVz-q0-Xpd" firstAttribute="leading" secondItem="jVH-JP-pJo" secondAttribute="leading" id="Eya-ig-8On"/>
- <constraint firstItem="hVz-q0-Xpd" firstAttribute="top" secondItem="jVH-JP-pJo" secondAttribute="top" id="UY2-bK-i8s"/>
- </constraints>
- </view>
- <blurEffect style="light"/>
- </visualEffectView>
- <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vxU-Mt-fMo" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="68.5" width="261" height="52.5"/>
- <accessibility key="accessibilityConfiguration" identifier="ConnectButton"/>
- <state key="normal" title="Secure my connection" backgroundImage="SuccessButton">
- <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- </state>
- <connections>
- <action selector="handleSecureConnection:" destination="7We-1v-DIF" eventType="touchUpInside" id="XmB-3v-mds"/>
- </connections>
- </button>
- </subviews>
- </stackView>
- </subviews>
- <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <constraints>
- <constraint firstItem="shY-Lj-oYx" firstAttribute="leading" secondItem="7l6-on-fbR" secondAttribute="leading" id="1th-7a-npG"/>
- <constraint firstItem="shY-Lj-oYx" firstAttribute="bottom" secondItem="7l6-on-fbR" secondAttribute="bottom" id="53Y-II-Op2"/>
- <constraint firstAttribute="trailing" secondItem="shY-Lj-oYx" secondAttribute="trailing" id="6QT-NE-4OX"/>
- <constraint firstItem="shY-Lj-oYx" firstAttribute="trailing" secondItem="7l6-on-fbR" secondAttribute="trailing" id="ORr-CI-hQE"/>
- <constraint firstItem="shY-Lj-oYx" firstAttribute="leading" secondItem="7l6-on-fbR" secondAttribute="leading" id="U5c-eT-w73"/>
- <constraint firstItem="shY-Lj-oYx" firstAttribute="top" secondItem="7l6-on-fbR" secondAttribute="top" id="cea-vh-q3j"/>
- <constraint firstAttribute="bottom" secondItem="shY-Lj-oYx" secondAttribute="bottom" id="isW-KM-CjB"/>
- <constraint firstItem="shY-Lj-oYx" firstAttribute="top" secondItem="7l6-on-fbR" secondAttribute="top" id="zkC-qT-d72"/>
- </constraints>
- </view>
- <view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="ZYQ-G2-MXc">
- <rect key="frame" x="0.0" y="0.0" width="261" height="120"/>
- <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
- <subviews>
- <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="K2E-tQ-f7f">
- <rect key="frame" x="0.0" y="0.0" width="261" height="120"/>
- <subviews>
- <visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HGl-4o-WUZ" customClass="TranslucentButtonBlurView" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
- <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="tbu-8S-1Wk">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
- <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
- <subviews>
- <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dbp-iY-d0K" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
- <accessibility key="accessibilityConfiguration" identifier="SwitchLocationButton"/>
- <inset key="contentEdgeInsets" minX="0.0" minY="0.0" maxX="0.0" maxY="10"/>
- <state key="normal" title="Switch location" backgroundImage="TranslucentNeutralButton">
- <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- </state>
- <connections>
- <action selector="handleSelectLocation:" destination="7We-1v-DIF" eventType="touchUpInside" id="8dc-Ta-6qx"/>
- </connections>
- </button>
- </subviews>
- <constraints>
- <constraint firstAttribute="bottom" secondItem="dbp-iY-d0K" secondAttribute="bottom" id="5LU-aU-B47"/>
- <constraint firstAttribute="trailing" secondItem="dbp-iY-d0K" secondAttribute="trailing" id="7PF-WL-1iS"/>
- <constraint firstItem="dbp-iY-d0K" firstAttribute="leading" secondItem="tbu-8S-1Wk" secondAttribute="leading" id="8h9-Dh-uGH"/>
- <constraint firstItem="dbp-iY-d0K" firstAttribute="top" secondItem="tbu-8S-1Wk" secondAttribute="top" id="Xop-Vl-s9E"/>
- </constraints>
- </view>
- <blurEffect style="light"/>
- </visualEffectView>
- <visualEffectView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" translatesAutoresizingMaskIntoConstraints="NO" id="wTZ-S5-pHI" customClass="TranslucentButtonBlurView" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="68" width="261" height="52"/>
- <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="hWm-jl-c2l">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
- <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
- <subviews>
- <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="PDP-HS-RB2" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
- <state key="normal" title="Cancel" backgroundImage="TranslucentDangerButton">
- <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- </state>
- <connections>
- <action selector="handleDisconnect:" destination="7We-1v-DIF" eventType="touchUpInside" id="lgN-mu-5IE"/>
- </connections>
- </button>
- </subviews>
- <constraints>
- <constraint firstItem="PDP-HS-RB2" firstAttribute="leading" secondItem="hWm-jl-c2l" secondAttribute="leading" id="0iK-cH-2T7"/>
- <constraint firstItem="PDP-HS-RB2" firstAttribute="top" secondItem="hWm-jl-c2l" secondAttribute="top" id="Q9r-wY-cgh"/>
- <constraint firstAttribute="bottom" secondItem="PDP-HS-RB2" secondAttribute="bottom" id="Rvj-jF-cwH"/>
- <constraint firstAttribute="trailing" secondItem="PDP-HS-RB2" secondAttribute="trailing" id="Thh-Vt-L4v"/>
- </constraints>
- </view>
- <blurEffect style="light"/>
- </visualEffectView>
- </subviews>
- </stackView>
- </subviews>
- <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <constraints>
- <constraint firstItem="K2E-tQ-f7f" firstAttribute="trailing" secondItem="ZYQ-G2-MXc" secondAttribute="trailing" id="8jj-NR-Sva"/>
- <constraint firstItem="K2E-tQ-f7f" firstAttribute="bottom" secondItem="ZYQ-G2-MXc" secondAttribute="bottom" id="eza-7A-uNf"/>
- <constraint firstItem="K2E-tQ-f7f" firstAttribute="top" secondItem="ZYQ-G2-MXc" secondAttribute="top" id="ndG-qN-nvg"/>
- <constraint firstItem="K2E-tQ-f7f" firstAttribute="leading" secondItem="ZYQ-G2-MXc" secondAttribute="leading" id="phY-um-kkn"/>
- <constraint firstAttribute="bottom" secondItem="K2E-tQ-f7f" secondAttribute="bottom" id="qUI-AH-MDO"/>
- <constraint firstItem="K2E-tQ-f7f" firstAttribute="top" secondItem="ZYQ-G2-MXc" secondAttribute="top" id="s3u-gd-29a"/>
- <constraint firstAttribute="trailing" secondItem="K2E-tQ-f7f" secondAttribute="trailing" id="x5Q-Uy-6xz"/>
- <constraint firstItem="K2E-tQ-f7f" firstAttribute="leading" secondItem="ZYQ-G2-MXc" secondAttribute="leading" id="xkx-qv-Dcr"/>
- </constraints>
- </view>
- <view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="KX2-Pi-Myg">
- <rect key="frame" x="0.0" y="0.0" width="261" height="120"/>
- <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
- <subviews>
- <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="J70-p3-IaC">
- <rect key="frame" x="0.0" y="0.0" width="261" height="120"/>
- <subviews>
- <visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fgw-rj-lK4" customClass="TranslucentButtonBlurView" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
- <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="dSR-qU-xgH">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
- <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
- <subviews>
- <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZbQ-zA-ZS8" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
- <accessibility key="accessibilityConfiguration" identifier="SwitchLocationButton"/>
- <inset key="contentEdgeInsets" minX="0.0" minY="0.0" maxX="0.0" maxY="10"/>
- <state key="normal" title="Switch location" backgroundImage="TranslucentNeutralButton">
- <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- </state>
- <connections>
- <action selector="handleSelectLocation:" destination="7We-1v-DIF" eventType="touchUpInside" id="rZA-gs-2O6"/>
- </connections>
- </button>
- </subviews>
- <constraints>
- <constraint firstItem="ZbQ-zA-ZS8" firstAttribute="leading" secondItem="dSR-qU-xgH" secondAttribute="leading" id="c2Y-VY-hME"/>
- <constraint firstAttribute="bottom" secondItem="ZbQ-zA-ZS8" secondAttribute="bottom" id="eUE-C6-e9C"/>
- <constraint firstAttribute="trailing" secondItem="ZbQ-zA-ZS8" secondAttribute="trailing" id="ggD-w4-oQQ"/>
- <constraint firstItem="ZbQ-zA-ZS8" firstAttribute="top" secondItem="dSR-qU-xgH" secondAttribute="top" id="oMx-RW-wv9"/>
- </constraints>
- </view>
- <blurEffect style="light"/>
- </visualEffectView>
- <visualEffectView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" translatesAutoresizingMaskIntoConstraints="NO" id="sAx-Or-5Yn" customClass="TranslucentButtonBlurView" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="68" width="261" height="52"/>
- <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="ANR-Gb-KsI">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
- <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
- <subviews>
- <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d5t-ia-qxF" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
- <accessibility key="accessibilityConfiguration" identifier="DisconnectButton"/>
- <state key="normal" title="Disconnect" backgroundImage="TranslucentDangerButton">
- <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- </state>
- <connections>
- <action selector="handleDisconnect:" destination="7We-1v-DIF" eventType="touchUpInside" id="S6r-fB-gnj"/>
- </connections>
- </button>
- </subviews>
- <constraints>
- <constraint firstAttribute="trailing" secondItem="d5t-ia-qxF" secondAttribute="trailing" id="6AH-65-3Vh"/>
- <constraint firstAttribute="bottom" secondItem="d5t-ia-qxF" secondAttribute="bottom" id="7aQ-oK-5Yo"/>
- <constraint firstItem="d5t-ia-qxF" firstAttribute="leading" secondItem="ANR-Gb-KsI" secondAttribute="leading" id="EKV-6Y-UtV"/>
- <constraint firstItem="d5t-ia-qxF" firstAttribute="top" secondItem="ANR-Gb-KsI" secondAttribute="top" id="NKf-WN-rVa"/>
- </constraints>
- </view>
- <blurEffect style="light"/>
- </visualEffectView>
- </subviews>
- </stackView>
- </subviews>
- <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <constraints>
- <constraint firstItem="J70-p3-IaC" firstAttribute="bottom" secondItem="KX2-Pi-Myg" secondAttribute="bottom" id="0FU-IO-L7p"/>
- <constraint firstItem="J70-p3-IaC" firstAttribute="top" secondItem="KX2-Pi-Myg" secondAttribute="top" id="KZn-7H-iDI"/>
- <constraint firstItem="J70-p3-IaC" firstAttribute="top" secondItem="KX2-Pi-Myg" secondAttribute="top" id="NnJ-v1-dtl"/>
- <constraint firstItem="J70-p3-IaC" firstAttribute="leading" secondItem="KX2-Pi-Myg" secondAttribute="leading" id="ZWy-eg-o1m"/>
- <constraint firstAttribute="trailing" secondItem="J70-p3-IaC" secondAttribute="trailing" id="cFk-ve-P9q"/>
- <constraint firstItem="J70-p3-IaC" firstAttribute="leading" secondItem="KX2-Pi-Myg" secondAttribute="leading" id="kjo-BJ-Odb"/>
- <constraint firstItem="J70-p3-IaC" firstAttribute="trailing" secondItem="KX2-Pi-Myg" secondAttribute="trailing" id="saI-Tt-ucW"/>
- <constraint firstAttribute="bottom" secondItem="J70-p3-IaC" secondAttribute="bottom" id="tBe-df-ho5"/>
- </constraints>
- </view>
- </objects>
- <point key="canvasLocation" x="2541" y="78"/>
- </scene>
</scenes>
<resources>
<image name="DangerButton" width="9" height="9"/>
@@ -1248,8 +1029,6 @@
<image name="IconSuccess" width="60" height="60"/>
<image name="LogoIcon" width="253" height="253"/>
<image name="SuccessButton" width="9" height="9"/>
- <image name="TranslucentDangerButton" width="9" height="9"/>
- <image name="TranslucentNeutralButton" width="9" height="9"/>
<namedColor name="Primary">
<color red="0.16078431372549021" green="0.30196078431372547" blue="0.45098039215686275" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
diff --git a/ios/MullvadVPN/Bundle+MullvadVersion.swift b/ios/MullvadVPN/Bundle+MullvadVersion.swift
deleted file mode 100644
index 872376c600..0000000000
--- a/ios/MullvadVPN/Bundle+MullvadVersion.swift
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-// Bundle+MullvadVersion.swift
-// MullvadVPN
-//
-// Created by pronebird on 29/11/2019.
-// Copyright © 2019 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-
-private let kInfoDictionaryMullvadVersionSuffixKey = "MullvadVersionSuffix"
-
-extension Bundle {
-
- var mullvadVersion: String? {
- let shortVersion = infoDictionary?["CFBundleShortVersionString"] as? String
- let versionSuffix = infoDictionary?[kInfoDictionaryMullvadVersionSuffixKey] as? String
-
- return [shortVersion, versionSuffix]
- .compactMap { $0 }
- .joined(separator: "-")
- }
-
-}
diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift
index 52b8a87f8c..53e5fd47ab 100644
--- a/ios/MullvadVPN/ConnectViewController.swift
+++ b/ios/MullvadVPN/ConnectViewController.swift
@@ -10,17 +10,19 @@ import UIKit
import NetworkExtension
import os
-class ConnectViewController: UIViewController,
- RootContainment,
- TunnelControlViewControllerDelegate,
- TunnelObserver,
+class ConnectViewController: UIViewController, RootContainment, TunnelObserver,
SelectLocationDelegate
{
-
@IBOutlet var secureLabel: UILabel!
@IBOutlet var countryLabel: UILabel!
@IBOutlet var cityLabel: UILabel!
@IBOutlet var connectionPanel: ConnectionPanelView!
+ @IBOutlet var buttonsStackView: UIStackView!
+
+ private let connectButton = makeButton(style: .success)
+ private let selectLocationButton = makeButton(style: .translucentNeutral)
+ private lazy var selectLocationBlurView = Self.makeBlurButton(button: selectLocationButton)
+ private let splitDisconnectButtonView = DisconnectSplitButton(bundle: nil)
private let alertPresenter = AlertPresenter()
@@ -47,15 +49,21 @@ class ConnectViewController: UIViewController,
setNeedsHeaderBarStyleAppearanceUpdate()
updateSecureLabel()
updateTunnelConnectionInfo()
+ updateButtons()
}
}
- private var showedAccountView = false
+ private var showedAccountView = true
override func viewDidLoad() {
super.viewDidLoad()
connectionPanel.collapseButton.addTarget(self, action: #selector(handleConnectionPanelButton(_:)), for: .touchUpInside)
+ connectButton.addTarget(self, action: #selector(handleConnect(_:)), for: .touchUpInside)
+ splitDisconnectButtonView.primaryButton.addTarget(self, action: #selector(handleDisconnect(_:)), for: .touchUpInside)
+ splitDisconnectButtonView.secondaryButton.addTarget(self, action: #selector(handleReconnect(_:)), for: .touchUpInside)
+
+ selectLocationButton.addTarget(self, action: #selector(handleSelectLocation(_:)), for: .touchUpInside)
TunnelManager.shared.addObserver(self)
self.tunnelState = TunnelManager.shared.tunnelState
@@ -67,14 +75,6 @@ class ConnectViewController: UIViewController,
showAccountViewForExpiredAccount()
}
- override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
- if case .embedTunnelControls = SegueIdentifier.Connect.from(segue: segue) {
- let tunnelControlController = segue.destination as! TunnelControlViewController
- tunnelControlController.view.translatesAutoresizingMaskIntoConstraints = false
- tunnelControlController.delegate = self
- }
- }
-
// MARK: - TunnelObserver
func tunnelStateDidChange(tunnelState: TunnelState) {
@@ -87,21 +87,6 @@ class ConnectViewController: UIViewController,
// no-op
}
- // MARK: - TunnelControlViewControllerDelegate
-
- func tunnelControlViewController(_ controller: TunnelControlViewController, handleAction action: TunnelControlAction) {
- switch action {
- case .connect:
- connectTunnel()
-
- case .disconnect:
- disconnectTunnel()
-
- case .selectLocation:
- showSelectLocation()
- }
- }
-
// MARK: - SelectLocationDelegate
func selectLocationController(_ controller: SelectLocationController, didSelectLocation location: RelayLocation) {
@@ -129,6 +114,63 @@ class ConnectViewController: UIViewController,
// MARK: - Private
+ private class func makeBlurButton(button: AppButton) -> UIView {
+ let effectView = TranslucentButtonBlurView(effect: UIBlurEffect(style: .light))
+ effectView.contentView.addSubview(button)
+
+ NSLayoutConstraint.activate([
+ button.topAnchor.constraint(equalTo: effectView.contentView.topAnchor),
+ button.leadingAnchor.constraint(equalTo: effectView.contentView.leadingAnchor),
+ button.trailingAnchor.constraint(equalTo: effectView.contentView.trailingAnchor),
+ button.bottomAnchor.constraint(equalTo: effectView.contentView.bottomAnchor)
+ ])
+
+ return effectView
+ }
+
+ private class func makeButton(style: AppButton.Style) -> AppButton {
+ let button = AppButton(type: .custom)
+ button.style = style
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
+ return button
+ }
+
+ private func updateButtons() {
+ switch tunnelState {
+ case .disconnected:
+ selectLocationButton.setTitle(NSLocalizedString("Select location", comment: ""), for: .normal)
+ connectButton.setTitle(NSLocalizedString("Secure connection", comment: ""), for: .normal)
+
+ setArrangedButtons([selectLocationBlurView, connectButton])
+
+ case .connecting:
+ selectLocationButton.setTitle(NSLocalizedString("Switch location", comment: ""), for: .normal)
+ splitDisconnectButtonView.primaryButton.setTitle(NSLocalizedString("Cancel", comment: ""), for: .normal)
+
+ setArrangedButtons([selectLocationBlurView, splitDisconnectButtonView])
+
+ case .connected, .reconnecting, .disconnecting:
+ selectLocationButton.setTitle(NSLocalizedString("Switch location", comment: ""), for: .normal)
+ splitDisconnectButtonView.primaryButton.setTitle(NSLocalizedString("Disconnect", comment: ""), for: .normal)
+
+ setArrangedButtons([selectLocationBlurView, splitDisconnectButtonView])
+ }
+ }
+
+ private func setArrangedButtons(_ newButtons: [UIView]) {
+ buttonsStackView.arrangedSubviews.forEach { (button) in
+ if !newButtons.contains(button) {
+ buttonsStackView.removeArrangedSubview(button)
+ button.removeFromSuperview()
+ }
+ }
+
+ newButtons.forEach { (button) in
+ buttonsStackView.addArrangedSubview(button)
+ }
+ }
+
private func updateSecureLabel() {
secureLabel.text = tunnelState.textForSecureLabel().uppercased()
secureLabel.textColor = tunnelState.textColorForSecureLabel()
@@ -208,6 +250,10 @@ class ConnectViewController: UIViewController,
}
}
+ private func reconnectTunnel() {
+ TunnelManager.shared.reconnectTunnel(completionHandler: nil)
+ }
+
private func showAccountViewForExpiredAccount() {
guard !showedAccountView else { return }
@@ -239,6 +285,22 @@ class ConnectViewController: UIViewController,
connectionPanel.toggleConnectionInfoVisibility()
}
+ @objc func handleConnect(_ sender: Any) {
+ connectTunnel()
+ }
+
+ @objc func handleDisconnect(_ sender: Any) {
+ disconnectTunnel()
+ }
+
+ @objc func handleReconnect(_ sender: Any) {
+ reconnectTunnel()
+ }
+
+ @objc func handleSelectLocation(_ sender: Any) {
+ showSelectLocation()
+ }
+
}
private extension TunnelState {
diff --git a/ios/MullvadVPN/ConnectionPanelView.swift b/ios/MullvadVPN/ConnectionPanelView.swift
index fbf0ae44d3..325fc17bc2 100644
--- a/ios/MullvadVPN/ConnectionPanelView.swift
+++ b/ios/MullvadVPN/ConnectionPanelView.swift
@@ -182,12 +182,12 @@ class ConnectionPanelCollapseButton: CustomButton {
enum Style {
case up, down
- var image: UIImage {
+ var image: UIImage? {
switch self {
case .up:
- return UIImage(imageLiteralResourceName: "IconChevronUp")
+ return UIImage(named: "IconChevronUp")
case .down:
- return UIImage(imageLiteralResourceName: "IconChevronDown")
+ return UIImage(named: "IconChevronDown")
}
}
}
diff --git a/ios/MullvadVPN/DisconnectSplitButton.swift b/ios/MullvadVPN/DisconnectSplitButton.swift
new file mode 100644
index 0000000000..a52d4e0af1
--- /dev/null
+++ b/ios/MullvadVPN/DisconnectSplitButton.swift
@@ -0,0 +1,59 @@
+//
+// DisconnectSplitButton.swift
+// MullvadVPN
+//
+// Created by pronebird on 29/07/2020.
+// Copyright © 2020 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+private let kSplitSeparatorWidth = CGFloat(1)
+
+class DisconnectSplitButton: UIView {
+ @IBOutlet var contentView: UIView!
+ @IBOutlet var primaryButton: AppButton!
+ @IBOutlet var secondaryButton: AppButton!
+
+ private var secondaryButtonObserver: NSObjectProtocol?
+
+ init(bundle: Bundle?) {
+ super.init(frame: .zero)
+
+ loadFromNib(bundle: bundle)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func loadFromNib(bundle: Bundle?) {
+ let nib = UINib(nibName: "DisconnectSplitButton", bundle: bundle)
+ _ = nib.instantiate(withOwner: self, options: nil)
+
+ primaryButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
+
+ contentView.translatesAutoresizingMaskIntoConstraints = false
+ addSubview(contentView)
+
+ NSLayoutConstraint.activate([
+ contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ contentView.topAnchor.constraint(equalTo: topAnchor),
+ contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
+ ])
+
+ secondaryButtonObserver = secondaryButton.observe(\.bounds, options: [.new]) { [weak self] (button, change) in
+ self?.adjustTitleLabelPosition()
+ }
+ }
+
+ private func adjustTitleLabelPosition() {
+ var contentInsets = AppButton.defaultContentInsets
+ contentInsets.left = secondaryButton.frame.width + kSplitSeparatorWidth
+ contentInsets.right = 0
+
+ primaryButton.contentEdgeInsets = contentInsets
+ }
+}
diff --git a/ios/MullvadVPN/DisconnectSplitButton.xib b/ios/MullvadVPN/DisconnectSplitButton.xib
new file mode 100644
index 0000000000..6b160f829b
--- /dev/null
+++ b/ios/MullvadVPN/DisconnectSplitButton.xib
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+ <device id="retina6_1" orientation="portrait" appearance="light"/>
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <objects>
+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="DisconnectSplitButton" customModule="MullvadVPN" customModuleProvider="target">
+ <connections>
+ <outlet property="contentView" destination="iN0-l3-epB" id="l9N-3B-7an"/>
+ <outlet property="primaryButton" destination="PD0-Fv-22K" id="dSp-cI-IXZ"/>
+ <outlet property="secondaryButton" destination="2ka-zL-tZX" id="U5c-9n-CJD"/>
+ </connections>
+ </placeholder>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+ <view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="iN0-l3-epB">
+ <rect key="frame" x="0.0" y="0.0" width="420" height="50"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <stackView opaque="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" spacing="1" translatesAutoresizingMaskIntoConstraints="NO" id="LYA-qF-TWz">
+ <rect key="frame" x="0.0" y="0.0" width="420" height="50"/>
+ <subviews>
+ <visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gDt-Zi-SNP" customClass="TranslucentButtonBlurView" customModule="MullvadVPN" customModuleProvider="target">
+ <rect key="frame" x="0.0" y="0.0" width="369" height="50"/>
+ <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="Z5z-dl-BIA">
+ <rect key="frame" x="0.0" y="0.0" width="369" height="50"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="PD0-Fv-22K" userLabel="Primary Button" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
+ <rect key="frame" x="0.0" y="0.0" width="369" height="50"/>
+ <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
+ <state key="normal" title="Button"/>
+ <userDefinedRuntimeAttributes>
+ <userDefinedRuntimeAttribute type="number" keyPath="interfaceBuilderStyle">
+ <integer key="value" value="5"/>
+ </userDefinedRuntimeAttribute>
+ </userDefinedRuntimeAttributes>
+ </button>
+ </subviews>
+ <constraints>
+ <constraint firstAttribute="trailing" secondItem="PD0-Fv-22K" secondAttribute="trailing" id="0sb-IJ-x0M"/>
+ <constraint firstItem="PD0-Fv-22K" firstAttribute="leading" secondItem="Z5z-dl-BIA" secondAttribute="leading" id="Ah6-1f-VuI"/>
+ <constraint firstItem="PD0-Fv-22K" firstAttribute="top" secondItem="Z5z-dl-BIA" secondAttribute="top" id="gfC-T2-1NN"/>
+ <constraint firstAttribute="bottom" secondItem="PD0-Fv-22K" secondAttribute="bottom" id="nto-wB-nPC"/>
+ </constraints>
+ </view>
+ <blurEffect style="regular"/>
+ </visualEffectView>
+ <visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XyY-R3-ytQ" customClass="TranslucentButtonBlurView" customModule="MullvadVPN" customModuleProvider="target">
+ <rect key="frame" x="370" y="0.0" width="50" height="50"/>
+ <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="N0x-Hd-Pi6">
+ <rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" horizontalCompressionResistancePriority="50" verticalCompressionResistancePriority="50" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2ka-zL-tZX" userLabel="Secondary Button" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
+ <rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
+ <constraints>
+ <constraint firstAttribute="width" secondItem="2ka-zL-tZX" secondAttribute="height" multiplier="1:1" id="oBq-BR-iqo"/>
+ </constraints>
+ <state key="normal" title="Button" image="IconReload"/>
+ <userDefinedRuntimeAttributes>
+ <userDefinedRuntimeAttribute type="number" keyPath="interfaceBuilderStyle">
+ <integer key="value" value="6"/>
+ </userDefinedRuntimeAttribute>
+ </userDefinedRuntimeAttributes>
+ </button>
+ </subviews>
+ <constraints>
+ <constraint firstAttribute="bottom" secondItem="2ka-zL-tZX" secondAttribute="bottom" id="3Sh-gX-GkA"/>
+ <constraint firstItem="2ka-zL-tZX" firstAttribute="leading" secondItem="N0x-Hd-Pi6" secondAttribute="leading" id="6ts-lS-vm6"/>
+ <constraint firstItem="2ka-zL-tZX" firstAttribute="top" secondItem="N0x-Hd-Pi6" secondAttribute="top" id="8HK-t6-YVW"/>
+ <constraint firstAttribute="trailing" secondItem="2ka-zL-tZX" secondAttribute="trailing" id="rvn-tI-FOp"/>
+ </constraints>
+ </view>
+ <blurEffect style="regular"/>
+ </visualEffectView>
+ </subviews>
+ <constraints>
+ <constraint firstItem="2ka-zL-tZX" firstAttribute="height" secondItem="PD0-Fv-22K" secondAttribute="height" id="3cN-0n-7HF"/>
+ </constraints>
+ </stackView>
+ </subviews>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ <constraints>
+ <constraint firstItem="LYA-qF-TWz" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="2kj-go-VNG"/>
+ <constraint firstAttribute="bottom" secondItem="LYA-qF-TWz" secondAttribute="bottom" id="C7Z-zY-N4F"/>
+ <constraint firstAttribute="trailing" secondItem="LYA-qF-TWz" secondAttribute="trailing" id="L01-AG-HOt"/>
+ <constraint firstItem="LYA-qF-TWz" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="d9k-Gy-lnD"/>
+ </constraints>
+ <nil key="simulatedTopBarMetrics"/>
+ <nil key="simulatedBottomBarMetrics"/>
+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+ <point key="canvasLocation" x="142.02898550724638" y="-25.446428571428569"/>
+ </view>
+ </objects>
+ <resources>
+ <image name="IconReload" width="512" height="512"/>
+ </resources>
+</document>
diff --git a/ios/MullvadVPN/LoginViewController.swift b/ios/MullvadVPN/LoginViewController.swift
index 20cb90d387..b6cb895389 100644
--- a/ios/MullvadVPN/LoginViewController.swift
+++ b/ios/MullvadVPN/LoginViewController.swift
@@ -220,11 +220,11 @@ class LoginViewController: UIViewController, RootContainment {
switch loginState {
case .failure:
let opacity: CGFloat = self.accountTextField.isEditing ? 0 : 1
- statusImageView.image = UIImage(imageLiteralResourceName: "IconFail")
+ statusImageView.image = UIImage(named: "IconFail")
animateStatusImage(to: opacity)
case .success:
- statusImageView.image = UIImage(imageLiteralResourceName: "IconSuccess")
+ statusImageView.image = UIImage(named: "IconSuccess")
animateStatusImage(to: 1)
case .default, .authenticating:
diff --git a/ios/MullvadVPN/SegueIdentifier.swift b/ios/MullvadVPN/SegueIdentifier.swift
index 3e9f2db97b..2edf12abeb 100644
--- a/ios/MullvadVPN/SegueIdentifier.swift
+++ b/ios/MullvadVPN/SegueIdentifier.swift
@@ -22,10 +22,6 @@ extension SegueIdentifier {
case showAccount = "ShowAccount"
}
- enum Connect: String, SegueConvertible {
- case embedTunnelControls = "EmbedTunnelControls"
- }
-
enum Account: String, SegueConvertible {
case logout = "Logout"
}
diff --git a/ios/MullvadVPN/SelectLocationCell.swift b/ios/MullvadVPN/SelectLocationCell.swift
index 8d29a1d960..0b5fa63dac 100644
--- a/ios/MullvadVPN/SelectLocationCell.swift
+++ b/ios/MullvadVPN/SelectLocationCell.swift
@@ -13,11 +13,11 @@ class SelectLocationCell: BasicTableViewCell {
let locationLabel = UILabel()
let statusIndicator = RelayStatusIndicatorView()
- let tickImageView = UIImageView(image: UIImage(imageLiteralResourceName: "IconTick"))
+ let tickImageView = UIImageView(image: UIImage(named: "IconTick"))
let collapseButton = UIButton(type: .custom)
- private let chevronDown = UIImage(imageLiteralResourceName: "IconChevronDown")
- private let chevronUp = UIImage(imageLiteralResourceName: "IconChevronUp")
+ private let chevronDown = UIImage(named: "IconChevronDown")
+ private let chevronUp = UIImage(named: "IconChevronUp")
var isDisabled = false {
didSet {
diff --git a/ios/MullvadVPN/SettingsViewController.swift b/ios/MullvadVPN/SettingsViewController.swift
index 4fd4f82a8a..bee3d3a458 100644
--- a/ios/MullvadVPN/SettingsViewController.swift
+++ b/ios/MullvadVPN/SettingsViewController.swift
@@ -91,8 +91,9 @@ class SettingsViewController: UITableViewController {
let middleSection = StaticTableViewSection()
let versionRow = StaticTableViewRow(reuseIdentifier: CellIdentifier.appVersion.rawValue) { (_, cell) in
let cell = cell as! SettingsAppVersionCell
+ let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
- cell.versionLabel.text = Bundle.main.mullvadVersion
+ cell.versionLabel.text = version
}
versionRow.isSelectable = false
diff --git a/ios/MullvadVPN/TranslucentButtonBlurView.swift b/ios/MullvadVPN/TranslucentButtonBlurView.swift
index 7679d7f251..87d44f8e47 100644
--- a/ios/MullvadVPN/TranslucentButtonBlurView.swift
+++ b/ios/MullvadVPN/TranslucentButtonBlurView.swift
@@ -27,6 +27,34 @@ private let kButtonCornerRadius = CGFloat(4)
private func setup() {
layer.cornerRadius = kButtonCornerRadius
layer.masksToBounds = true
+
+ updateCornerMask()
+ }
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ updateCornerMask()
+ }
+
+ private func updateCornerMask() {
+ for case let button as AppButton in contentView.subviews {
+ layer.maskedCorners = Self.cornerMask(buttonStyle: button.style)
+ }
+ }
+
+ private class func cornerMask(buttonStyle: AppButton.Style) -> CACornerMask {
+ switch buttonStyle {
+ case .translucentDangerSplitLeft:
+ return [.layerMinXMinYCorner, .layerMinXMaxYCorner]
+ case .translucentDangerSplitRight:
+ return [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
+ default:
+ return [
+ .layerMinXMinYCorner, .layerMinXMaxYCorner,
+ .layerMaxXMinYCorner, .layerMaxXMaxYCorner
+ ]
+ }
}
}
diff --git a/ios/MullvadVPN/TunnelControlViewController.swift b/ios/MullvadVPN/TunnelControlViewController.swift
deleted file mode 100644
index c380bafdd6..0000000000
--- a/ios/MullvadVPN/TunnelControlViewController.swift
+++ /dev/null
@@ -1,104 +0,0 @@
-//
-// TunnelControlView.swift
-// MullvadVPN
-//
-// Created by pronebird on 01/11/2019.
-// Copyright © 2019 Mullvad VPN AB. All rights reserved.
-//
-
-import UIKit
-
-enum TunnelControlAction {
- /// An action emitted only when the tunnel is down
- case connect
-
- /// An action emitted when user either selects to cancel the connection or disconnect when
- /// the tunnel is already connected
- case disconnect
-
- /// An action emitted when user requests to either select the location when the tunnel is down
- /// or change the location when the tunnel is connecting or connected.
- case selectLocation
-}
-
-protocol TunnelControlViewControllerDelegate: class {
- func tunnelControlViewController(_ controller: TunnelControlViewController, handleAction action: TunnelControlAction) -> Void
-}
-
-class TunnelControlViewController: UIViewController, TunnelObserver {
-
- @IBOutlet var disconnectedView: UIView!
- @IBOutlet var connectingView: UIView!
- @IBOutlet var connectedView: UIView!
-
- weak var delegate: TunnelControlViewControllerDelegate?
-
- private var controlsView: UIView?
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- TunnelManager.shared.addObserver(self)
- self.didReceiveTunnelState(TunnelManager.shared.tunnelState)
- }
-
- // MARK: - TunnelObserver
-
- func tunnelStateDidChange(tunnelState: TunnelState) {
- DispatchQueue.main.async {
- self.didReceiveTunnelState(tunnelState)
- }
- }
-
- func tunnelPublicKeyDidChange(publicKey: WireguardPublicKey?) {
- // no-op
- }
-
- /// MARK: - Private
-
- private func didReceiveTunnelState(_ tunnelState: TunnelState) {
- switch tunnelState {
- case .disconnected:
- addControlsView(disconnectedView)
-
- case .connecting:
- addControlsView(connectingView)
-
- case .connected, .reconnecting, .disconnecting:
- addControlsView(connectedView)
- }
- }
-
- private func addControlsView(_ nextControlsView: UIView) {
- guard controlsView != nextControlsView else { return }
-
- controlsView?.removeFromSuperview()
- controlsView = nextControlsView
-
- nextControlsView.translatesAutoresizingMaskIntoConstraints = false
-
- view.addSubview(nextControlsView)
-
- NSLayoutConstraint.activate([
- nextControlsView.topAnchor.constraint(equalTo: view.topAnchor),
- nextControlsView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- nextControlsView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- nextControlsView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- ])
- }
-
- // MARK: - Actions
-
- @IBAction func handleSecureConnection(_ sender: Any) {
- delegate?.tunnelControlViewController(self, handleAction: .connect)
- }
-
- @IBAction func handleDisconnect(_ sender: Any) {
- delegate?.tunnelControlViewController(self, handleAction: .disconnect)
- }
-
- @IBAction func handleSelectLocation(_ sender: Any) {
- delegate?.tunnelControlViewController(self, handleAction: .selectLocation)
- }
-
-}
diff --git a/ios/MullvadVPN/TunnelManager.swift b/ios/MullvadVPN/TunnelManager.swift
index ec234a348f..f45daca22a 100644
--- a/ios/MullvadVPN/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager.swift
@@ -435,6 +435,28 @@ class TunnelManager {
exclusityController.addOperation(operation, categories: [.tunnelControl])
}
+ func reconnectTunnel(completionHandler: (() -> Void)?) {
+ let operation = AsyncBlockOperation { (finish) in
+ guard let tunnelIpc = self.tunnelIpc else {
+ finish()
+ return
+ }
+
+ tunnelIpc.reloadTunnelSettings { (result) in
+ if case .failure(let error) = result {
+ error.logChain(message: "Failed to reconnect the tunnel")
+ }
+ finish()
+ }
+ }
+
+ operation.addDidFinishBlockObserver { (operation) in
+ completionHandler?()
+ }
+
+ exclusityController.addOperation(operation, categories: [.tunnelControl])
+ }
+
func setAccount(accountToken: String, completionHandler: @escaping (Result<(), TunnelManager.Error>) -> Void) {
let operation = ResultOperation<(), TunnelManager.Error> { (finish) in
let result = Self.makeTunnelSettings(accountToken: accountToken)
diff --git a/ios/convert-assets.rb b/ios/convert-assets.rb
index a082667e04..0d49431304 100755
--- a/ios/convert-assets.rb
+++ b/ios/convert-assets.rb
@@ -28,6 +28,7 @@ GRAPHICAL_ASSETS=[
"icon-fail.svg",
"icon-fastest.svg",
"icon-nearest.svg",
+ "icon-reload.svg",
"icon-settings.svg",
"icon-spinner.svg",
"icon-success.svg",
@@ -76,7 +77,9 @@ ADDITIONAL_ASSETS = [
"SuccessButton.svg",
"DangerButton.svg",
"TranslucentDangerButton.svg",
- "TranslucentNeutralButton.svg"
+ "TranslucentNeutralButton.svg",
+ "TranslucentDangerSplitLeftButton.svg",
+ "TranslucentDangerSplitRightButton.svg"
]
# Functions
@@ -113,6 +116,9 @@ def genereate_app_icon()
end
def generate_additional_assets()
+ # Fix PDF "CreationDate" for reproducible output
+ environment_variables = { "SOURCE_DATE_EPOCH" => "1596022781" }
+
for asset_name in ADDITIONAL_ASSETS do
svg_file = File.join(ADDITIONAL_ASSETS_DIR, asset_name)
image_name = File.basename(svg_file, ".svg")
@@ -125,7 +131,7 @@ def generate_additional_assets()
end
puts "Generate #{image_name} -> #{output_file}"
- system("rsvg-convert", "-f", "pdf", "-o", output_file, svg_file)
+ system(environment_variables, "rsvg-convert", "-f", "pdf", "-o", output_file, svg_file)
end
end