summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2020-02-17 16:02:55 +0100
committerAndrej Mihajlov <and@mullvad.net>2020-02-18 13:02:48 +0100
commit8cabbf30adedaea8969080804c2e335e0625663b (patch)
tree3dd98c99cdf3604e6d79996fc3ce64cae38648de
parent354c7feec88797e6696dbbc7a5177187baae3a2e (diff)
downloadmullvadvpn-8cabbf30adedaea8969080804c2e335e0625663b.tar.xz
mullvadvpn-8cabbf30adedaea8969080804c2e335e0625663b.zip
Show relay location and connection info
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj16
-rw-r--r--ios/MullvadVPN/Base.lproj/Main.storyboard81
-rw-r--r--ios/MullvadVPN/ConnectViewController.swift33
-rw-r--r--ios/MullvadVPN/ConnectionPanelView.swift240
-rw-r--r--ios/MullvadVPN/GeoLocation.swift16
-rw-r--r--ios/MullvadVPN/PacketTunnelIpc.swift4
-rw-r--r--ios/MullvadVPN/RelaySelector.swift39
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProviderHost.swift9
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider.swift4
9 files changed, 399 insertions, 43 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 5a892a9d45..9d99625ea2 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -62,6 +62,9 @@
588AE730236200E2009F9F2E /* MutuallyExclusive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588AE72E2362001F009F9F2E /* MutuallyExclusive.swift */; };
5894FC492296A8090017471D /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5894FC482296A8090017471D /* CustomButton.swift */; };
589AB4F7227B64450039131E /* BasicTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 589AB4F6227B64450039131E /* BasicTableViewCell.swift */; };
+ 58A1AA8723F43901009F7EA6 /* GeoLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8623F43901009F7EA6 /* GeoLocation.swift */; };
+ 58A1AA8823F43901009F7EA6 /* GeoLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8623F43901009F7EA6 /* GeoLocation.swift */; };
+ 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */; };
58A8BE81239FBE62006B74AC /* IPEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58561C98239A5D1500BD6B5E /* IPEndpoint.swift */; };
58A8BE8323A0F362006B74AC /* UIAlertController+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A8BE8223A0F362006B74AC /* UIAlertController+Error.swift */; };
58ADDB3C227B1BD200FAFEA7 /* JsonRpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ADDB3B227B1BD200FAFEA7 /* JsonRpc.swift */; };
@@ -204,6 +207,8 @@
5894E725236B2801008A2793 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
5894FC482296A8090017471D /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = "<group>"; };
589AB4F6227B64450039131E /* BasicTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicTableViewCell.swift; sourceTree = "<group>"; };
+ 58A1AA8623F43901009F7EA6 /* GeoLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoLocation.swift; sourceTree = "<group>"; };
+ 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPanelView.swift; sourceTree = "<group>"; };
58A8BE8223A0F362006B74AC /* UIAlertController+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Error.swift"; sourceTree = "<group>"; };
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>"; };
@@ -344,11 +349,13 @@
5845F839236C6A7200B2D93C /* AutoDisposableSink.swift */,
589AB4F6227B64450039131E /* BasicTableViewCell.swift */,
58EC4E6B23915325003F5C5B /* Bundle+MullvadVersion.swift */,
+ 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */,
58CCA00F224249A1004F3011 /* ConnectViewController.swift */,
5894FC482296A8090017471D /* CustomButton.swift */,
582BB1B0229569620055B6EF /* CustomNavigationBar.swift */,
58C6B35D22BBBFE3003C19AD /* Data+HexCoding.swift */,
5873884C239E6D7E00E96C4E /* EmbeddedViewContainerView.swift */,
+ 58A1AA8623F43901009F7EA6 /* GeoLocation.swift */,
58CE5E6F224146210008646E /* Info.plist */,
5840250022B1124600E4CFEC /* IpAddress+Codable.swift */,
58C6B34E22BB7AC0003C19AD /* IPAddressRange.swift */,
@@ -356,6 +363,7 @@
58ADDB3B227B1BD200FAFEA7 /* JsonRpc.swift */,
58AEEF642344A36000C9BBD5 /* KeychainError.swift */,
58CE5E6C224146210008646E /* LaunchScreen.storyboard */,
+ 58BA692D23E99EFF009DC256 /* Locking.swift */,
587B08DF229433EB000E6F17 /* LoginState.swift */,
58CE5E65224146200008646E /* LoginViewController.swift */,
58CE5E67224146200008646E /* Main.storyboard */,
@@ -382,6 +390,8 @@
5877152D23981C5B001F8237 /* SettingsBasicCell.swift */,
582BB1AE229566420055B6EF /* SettingsCell.swift */,
58CCA01122424D11004F3011 /* SettingsViewController.swift */,
+ 58BA693023EADA6A009DC256 /* SimulatorTunnelProvider.swift */,
+ 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */,
58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */,
581CBCED229826FD00727D7F /* StaticTableViewDataSource.swift */,
5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */,
@@ -398,9 +408,6 @@
58B8743122B25A7600015324 /* WireguardAssociatedAddresses.swift */,
5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */,
58C6B35322BB87C4003C19AD /* WireguardPrivateKey.swift */,
- 58BA692D23E99EFF009DC256 /* Locking.swift */,
- 58BA693023EADA6A009DC256 /* SimulatorTunnelProvider.swift */,
- 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */,
);
path = MullvadVPN;
sourceTree = "<group>";
@@ -698,6 +705,7 @@
5840250122B1124600E4CFEC /* IpAddress+Codable.swift in Sources */,
58EC4E6C23915325003F5C5B /* Bundle+MullvadVersion.swift in Sources */,
58BA693123EADA6A009DC256 /* SimulatorTunnelProvider.swift in Sources */,
+ 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */,
582BB1B52295780F0055B6EF /* AccountExpiry.swift in Sources */,
582BB1B3229574F40055B6EF /* SettingsAccountCell.swift in Sources */,
58CCA010224249A1004F3011 /* ConnectViewController.swift in Sources */,
@@ -733,6 +741,7 @@
5877152E23981C5B001F8237 /* SettingsBasicCell.swift in Sources */,
5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */,
58ADDB3C227B1BD200FAFEA7 /* JsonRpc.swift in Sources */,
+ 58A1AA8723F43901009F7EA6 /* GeoLocation.swift in Sources */,
581CBCEE229826FD00727D7F /* StaticTableViewDataSource.swift in Sources */,
58CE5E64224146200008646E /* AppDelegate.swift in Sources */,
58C6B35E22BBBFE3003C19AD /* Data+HexCoding.swift in Sources */,
@@ -792,6 +801,7 @@
58561C9A239A5D1500BD6B5E /* IPEndpoint.swift in Sources */,
584B26FF237435A90073B10E /* RelaySelector+RelayCache.swift in Sources */,
58781CCE22AE8918009B9D8E /* RelayConstraints.swift in Sources */,
+ 58A1AA8823F43901009F7EA6 /* GeoLocation.swift in Sources */,
58781CD522AFBA39009B9D8E /* RelaySelector.swift in Sources */,
5845F843236CBDAB00B2D93C /* PacketTunnelIpc.swift in Sources */,
);
diff --git a/ios/MullvadVPN/Base.lproj/Main.storyboard b/ios/MullvadVPN/Base.lproj/Main.storyboard
index aec8dd5f4f..45acb84b43 100644
--- a/ios/MullvadVPN/Base.lproj/Main.storyboard
+++ b/ios/MullvadVPN/Base.lproj/Main.storyboard
@@ -275,22 +275,52 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
- <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="3rI-k6-N1S">
- <rect key="frame" x="24" y="304" width="327" height="59"/>
+ <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"/>
<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="26.5"/>
+ <rect key="frame" x="0.0" y="0.0" width="327" height="24"/>
<accessibility key="accessibilityConfiguration" identifier="SecureConnectionLabel"/>
- <fontDescription key="fontDescription" type="system" pointSize="22"/>
+ <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
<color key="textColor" name="Success"/>
<nil key="highlightedColor"/>
</label>
- <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sweden" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9Yf-sl-l3q">
- <rect key="frame" x="0.0" y="32.5" width="327" height="26.5"/>
- <fontDescription key="fontDescription" type="system" pointSize="22"/>
- <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
- <nil key="highlightedColor"/>
- </label>
+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="PBy-k6-ijS">
+ <rect key="frame" x="0.0" y="32" width="327" height="82"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Stockholm" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mfr-Ic-PYf">
+ <rect key="frame" x="0.0" y="0.0" width="327" height="41"/>
+ <fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
+ <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" text="Sweden" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9Yf-sl-l3q">
+ <rect key="frame" x="0.0" y="41" width="327" height="41"/>
+ <fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
+ <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ </stackView>
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hCl-ph-fhs">
+ <rect key="frame" x="0.0" y="122" width="327" height="90"/>
+ <subviews>
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ocV-9f-WDZ" customClass="ConnectionPanelView" customModule="MullvadVPN" customModuleProvider="target">
+ <rect key="frame" x="0.0" y="0.0" width="327" height="90"/>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="90" placeholder="YES" id="z9Q-OD-1co"/>
+ </constraints>
+ </view>
+ </subviews>
+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+ <constraints>
+ <constraint firstItem="ocV-9f-WDZ" firstAttribute="top" secondItem="hCl-ph-fhs" secondAttribute="top" id="S3C-dT-RKK"/>
+ <constraint firstItem="ocV-9f-WDZ" firstAttribute="leading" secondItem="hCl-ph-fhs" secondAttribute="leading" id="e8V-PF-ISy"/>
+ <constraint firstAttribute="bottom" secondItem="ocV-9f-WDZ" secondAttribute="bottom" id="ehS-MG-cf7"/>
+ <constraint firstAttribute="trailing" secondItem="ocV-9f-WDZ" secondAttribute="trailing" id="jIg-r7-5mz"/>
+ </constraints>
+ </view>
</subviews>
</stackView>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Dv1-XA-XQG" userLabel="Tunnel control buttons">
@@ -306,9 +336,9 @@
<color key="backgroundColor" name="Primary"/>
<constraints>
<constraint firstItem="3rI-k6-N1S" firstAttribute="leading" secondItem="PNd-mm-N1B" secondAttribute="leadingMargin" id="5fs-z1-QKf"/>
- <constraint firstItem="3rI-k6-N1S" firstAttribute="centerY" secondItem="PNd-mm-N1B" secondAttribute="centerY" id="MU7-g1-1QL"/>
<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"/>
</constraints>
@@ -316,7 +346,9 @@
<viewLayoutGuide key="safeArea" id="iBo-pG-OTz"/>
</view>
<connections>
- <outlet property="countryLabel" destination="9Yf-sl-l3q" id="TZY-gM-Qr8"/>
+ <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"/>
<outlet property="secureLabel" destination="HNy-mU-nui" id="QBg-mR-Z6g"/>
<segue destination="hOC-Ab-N3D" kind="presentation" identifier="ShowRelaySelector" id="mui-V1-CK4"/>
</connections>
@@ -337,20 +369,20 @@
<color key="separatorColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Account" id="ghE-jC-RWf" customClass="SettingsAccountCell" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="55.5" width="375" height="43.5"/>
+ <rect key="frame" x="0.0" y="55.5" width="375" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ghE-jC-RWf" id="sTl-gI-g2a">
- <rect key="frame" x="0.0" y="0.0" width="348" height="43.5"/>
+ <rect key="frame" x="0.0" y="0.0" width="348" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Account" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Lve-Kd-qTr">
- <rect key="frame" x="16" y="11" width="63.5" height="21.5"/>
+ <rect key="frame" x="16" y="11" width="63.5" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="A YEAR LEFT" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QeD-EQ-Ruo">
- <rect key="frame" x="259" y="11" width="81" height="21.5"/>
+ <rect key="frame" x="259" y="11" width="81" height="21"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
@@ -375,20 +407,20 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="AppVersion" id="pbd-iC-Emm" customClass="SettingsAppVersionCell" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="99" width="375" height="43.5"/>
+ <rect key="frame" x="0.0" y="98.5" width="375" height="43"/>
<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"/>
+ <rect key="frame" x="0.0" y="0.0" width="375" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="App version" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pYC-Zb-8N9">
- <rect key="frame" x="16" y="11" width="91" height="21.5"/>
+ <rect key="frame" x="16" y="11" width="91" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="2018.3" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sOr-vj-cg7">
- <rect key="frame" x="316.5" y="11" width="42.5" height="21.5"/>
+ <rect key="frame" x="316.5" y="11" width="42.5" height="21"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="13"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
@@ -410,14 +442,14 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="BasicDisclosure" id="Ahs-gu-nTM" customClass="SettingsBasicCell" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="142.5" width="375" height="43.5"/>
+ <rect key="frame" x="0.0" y="141.5" width="375" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Ahs-gu-nTM" id="Drq-vk-8F2">
- <rect key="frame" x="0.0" y="0.0" width="348" height="43.5"/>
+ <rect key="frame" x="0.0" y="0.0" width="348" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Amw-A3-ePS">
- <rect key="frame" x="16" y="11" width="324" height="21.5"/>
+ <rect key="frame" x="16" y="11" width="324" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
@@ -1057,7 +1089,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hVz-q0-Xpd" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="0.0" y="0.0" width="261" height="52.5"/>
+ <rect key="frame" x="0.0" y="0.0" width="261" height="52"/>
<inset key="contentEdgeInsets" minX="0.0" minY="0.0" maxX="0.0" maxY="10"/>
<state key="normal" title="Select location" backgroundImage="TranslucentNeutralButton">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -1067,6 +1099,7 @@
</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"/>
diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift
index c5ee619982..b16293d769 100644
--- a/ios/MullvadVPN/ConnectViewController.swift
+++ b/ios/MullvadVPN/ConnectViewController.swift
@@ -15,6 +15,8 @@ class ConnectViewController: UIViewController, RootContainment, TunnelControlVie
@IBOutlet var secureLabel: UILabel!
@IBOutlet var countryLabel: UILabel!
+ @IBOutlet var cityLabel: UILabel!
+ @IBOutlet var connectionPanel: ConnectionPanelView!
private var setRelaysSubscriber: AnyCancellable?
private var startStopTunnelSubscriber: AnyCancellable?
@@ -45,6 +47,8 @@ class ConnectViewController: UIViewController, RootContainment, TunnelControlVie
override func viewDidLoad() {
super.viewDidLoad()
+ connectionPanel.collapseButton.addTarget(self, action: #selector(handleConnectionPanelButton(_:)), for: .touchUpInside)
+
tunnelStateSubscriber = TunnelManager.shared.$tunnelState
.receive(on: DispatchQueue.main)
.assign(to: \.tunnelState, on: self)
@@ -76,18 +80,37 @@ class ConnectViewController: UIViewController, RootContainment, TunnelControlVie
// MARK: - Private
private func updateSecureLabel() {
- secureLabel.text = tunnelState.textForSecureLabel()
+ secureLabel.text = tunnelState.textForSecureLabel().uppercased()
secureLabel.textColor = tunnelState.textColorForSecureLabel()
}
+ private func attributedStringForLocation(string: String) -> NSAttributedString {
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.lineSpacing = 0
+ paragraphStyle.lineHeightMultiple = 0.80
+ return NSAttributedString(string: string, attributes: [
+ .paragraphStyle: paragraphStyle])
+ }
+
private func updateTunnelConnectionInfo() {
switch tunnelState {
case .connected(let connectionInfo),
.reconnecting(let connectionInfo):
- countryLabel.text = "\(connectionInfo.hostname)\nIn: \(connectionInfo.ipv4Relay)"
+ cityLabel.attributedText = attributedStringForLocation(string: connectionInfo.geoLocation.city)
+ countryLabel.attributedText = attributedStringForLocation(string: connectionInfo.geoLocation.country)
+
+ connectionPanel.dataSource = ConnectionPanelData(
+ inAddress: "\(connectionInfo.ipv4Relay) UDP",
+ outAddress: nil
+ )
+ connectionPanel.isHidden = false
+ connectionPanel.collapseButton.setTitle(connectionInfo.hostname, for: .normal)
case .connecting, .disconnected, .disconnecting:
- countryLabel.text = ""
+ cityLabel.attributedText = attributedStringForLocation(string: " ")
+ countryLabel.attributedText = attributedStringForLocation(string: " ")
+ connectionPanel.dataSource = nil
+ connectionPanel.isHidden = true
}
}
@@ -114,6 +137,10 @@ class ConnectViewController: UIViewController, RootContainment, TunnelControlVie
// MARK: - Actions
+ @objc func handleConnectionPanelButton(_ sender: Any) {
+ connectionPanel.toggleConnectionInfoVisibility()
+ }
+
@IBAction func unwindFromSelectLocation(segue: UIStoryboardSegue) {
guard let selectLocationController = segue.source as? SelectLocationController else { return }
guard let selectedLocation = selectLocationController.selectedLocation else { return }
diff --git a/ios/MullvadVPN/ConnectionPanelView.swift b/ios/MullvadVPN/ConnectionPanelView.swift
new file mode 100644
index 0000000000..85ed53a12e
--- /dev/null
+++ b/ios/MullvadVPN/ConnectionPanelView.swift
@@ -0,0 +1,240 @@
+//
+// ConnectionPanelView.swift
+// MullvadVPN
+//
+// Created by pronebird on 12/02/2020.
+// Copyright © 2020 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+struct ConnectionPanelData {
+ var inAddress: String
+ var outAddress: String?
+}
+
+class ConnectionPanelView: UIView {
+
+ var dataSource: ConnectionPanelData? {
+ didSet {
+ didChangeDataSource()
+ }
+ }
+
+ var showsConnectionInfo = false {
+ didSet {
+ updateConnectionInfoVisibility()
+ }
+ }
+
+ let collapseButton: ConnectionPanelCollapseButton = {
+ let button = ConnectionPanelCollapseButton(type: .custom)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.tintColor = .white
+ return button
+ }()
+
+ private let protocolRow = ConnectionPanelProtocolTypeRow()
+ private let inAddressRow = ConnectionPanelAddressRow()
+ private let outAddressRow = ConnectionPanelAddressRow()
+
+ private lazy var stackView: UIStackView = {
+ let stackView = UIStackView(arrangedSubviews: [protocolRow, inAddressRow, outAddressRow])
+ stackView.axis = .vertical
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ return stackView
+ }()
+
+ private let textLabelLayoutGuide: UILayoutGuide = {
+ let layoutGuide = UILayoutGuide()
+ layoutGuide.identifier = "TextLabelLayoutGuide"
+ return layoutGuide
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ commonInit()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ commonInit()
+ }
+
+ func didChangeDataSource() {
+ inAddressRow.detailTextLabel.text = dataSource?.inAddress
+ outAddressRow.detailTextLabel.text = dataSource?.outAddress
+ }
+
+ func toggleConnectionInfoVisibility() {
+ showsConnectionInfo = !showsConnectionInfo
+ }
+
+ private func updateConnectionInfoVisibility() {
+ stackView.isHidden = showsConnectionInfo
+ collapseButton.style = showsConnectionInfo ? .down : .up
+ }
+
+ private func commonInit() {
+ protocolRow.translatesAutoresizingMaskIntoConstraints = false
+ inAddressRow.translatesAutoresizingMaskIntoConstraints = false
+ outAddressRow.translatesAutoresizingMaskIntoConstraints = false
+
+ // TODO: Unhide it when we have out address
+ outAddressRow.isHidden = true
+
+ protocolRow.textLabel.text = NSLocalizedString("WireGuard", comment: "")
+ inAddressRow.textLabel.text = NSLocalizedString("In", comment: "")
+ outAddressRow.textLabel.text = NSLocalizedString("Out", comment: "")
+
+ addSubview(collapseButton)
+ addSubview(stackView)
+ addLayoutGuide(textLabelLayoutGuide)
+
+ NSLayoutConstraint.activate([
+ collapseButton.topAnchor.constraint(equalTo: topAnchor),
+ collapseButton.leadingAnchor.constraint(equalTo: leadingAnchor),
+ collapseButton.trailingAnchor.constraint(equalTo: trailingAnchor),
+
+ stackView.topAnchor.constraint(equalTo: collapseButton.bottomAnchor, constant: 4),
+ stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
+
+ // Align all text labels with the guide, so that they maintain equal width
+ textLabelLayoutGuide.trailingAnchor
+ .constraint(equalTo: inAddressRow.textLabel.trailingAnchor),
+ textLabelLayoutGuide.trailingAnchor
+ .constraint(equalTo: outAddressRow.textLabel.trailingAnchor)
+ ])
+ }
+}
+
+class ConnectionPanelProtocolTypeRow: UIView {
+ let textLabel = UILabel()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ textLabel.translatesAutoresizingMaskIntoConstraints = false
+ textLabel.font = UIFont.systemFont(ofSize: 17)
+ textLabel.textColor = .white
+
+ addSubview(textLabel)
+
+ NSLayoutConstraint.activate([
+ textLabel.topAnchor.constraint(equalTo: topAnchor),
+ textLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
+ textLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
+ textLabel.trailingAnchor.constraint(equalTo: trailingAnchor)
+ ])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+class ConnectionPanelAddressRow: UIView {
+ let textLabel = UILabel()
+ let detailTextLabel = UILabel()
+ let stackView: UIStackView
+
+ override init(frame: CGRect) {
+
+ let font = UIFont.systemFont(ofSize: 17)
+
+ textLabel.font = font
+ textLabel.textColor = .white
+ textLabel.translatesAutoresizingMaskIntoConstraints = false
+ textLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+
+ detailTextLabel.font = font
+ detailTextLabel.textColor = .white
+ detailTextLabel.translatesAutoresizingMaskIntoConstraints = false
+
+ stackView = UIStackView(arrangedSubviews: [textLabel, detailTextLabel])
+ stackView.spacing = UIStackView.spacingUseSystem
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+
+ super.init(frame: frame)
+
+ addSubview(stackView)
+
+ NSLayoutConstraint.activate([
+ stackView.topAnchor.constraint(equalTo: topAnchor),
+ stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
+ stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ stackView.trailingAnchor.constraint(equalTo: trailingAnchor)
+ ])
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+class ConnectionPanelCollapseButton: UIButton {
+
+ enum Style {
+ case up, down
+
+ var image: UIImage {
+ switch self {
+ case .up:
+ return UIImage(imageLiteralResourceName: "IconChevronUp")
+ case .down:
+ return UIImage(imageLiteralResourceName: "IconChevronDown")
+ }
+ }
+ }
+
+ var style = Style.up {
+ didSet {
+ updateButtonImage()
+ }
+ }
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ commonInit()
+ updateButtonImage()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+
+ commonInit()
+ updateButtonImage()
+ }
+
+ private func commonInit() {
+ setTitleColor(UIColor.white, for: .normal)
+ setTitleColor(UIColor.lightGray, for: .highlighted)
+ setTitleColor(UIColor.lightGray, for: .disabled)
+ }
+
+ private func updateButtonImage() {
+ setImage(style.image, for: .normal)
+ }
+
+ override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
+ let titleRect = self.titleRect(forContentRect: contentRect)
+ var imageRect = super.imageRect(forContentRect: contentRect)
+
+ imageRect.origin.x = titleRect.maxX
+
+ return imageRect
+ }
+
+ override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
+ var titleRect = super.titleRect(forContentRect: contentRect)
+
+ titleRect.origin.x = 0
+
+ return titleRect
+ }
+
+}
diff --git a/ios/MullvadVPN/GeoLocation.swift b/ios/MullvadVPN/GeoLocation.swift
new file mode 100644
index 0000000000..80db85a81f
--- /dev/null
+++ b/ios/MullvadVPN/GeoLocation.swift
@@ -0,0 +1,16 @@
+//
+// GeoLocation.swift
+// MullvadVPN
+//
+// Created by pronebird on 12/02/2020.
+// Copyright © 2020 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+struct GeoLocation: Codable, Equatable {
+ var country: String
+ var city: String
+ var latitude: Double
+ var longitude: Double
+}
diff --git a/ios/MullvadVPN/PacketTunnelIpc.swift b/ios/MullvadVPN/PacketTunnelIpc.swift
index 4dd581c6aa..6362b2edb8 100644
--- a/ios/MullvadVPN/PacketTunnelIpc.swift
+++ b/ios/MullvadVPN/PacketTunnelIpc.swift
@@ -52,13 +52,15 @@ struct TunnelConnectionInfo: Codable, Equatable {
let ipv4Relay: IPv4Endpoint
let ipv6Relay: IPv6Endpoint?
let hostname: String
+ let geoLocation: GeoLocation
}
extension TunnelConnectionInfo: CustomDebugStringConvertible {
var debugDescription: String {
return "{ ipv4Relay: \(String(reflecting: ipv4Relay)), " +
"ipv6Relay: \(String(reflecting: ipv6Relay)), " +
- "hostname: \(String(reflecting: hostname)) }"
+ "hostname: \(String(reflecting: hostname))," +
+ "geoLocation: \(String(reflecting: geoLocation)) }"
}
}
diff --git a/ios/MullvadVPN/RelaySelector.swift b/ios/MullvadVPN/RelaySelector.swift
index d863365174..4ff29ef41d 100644
--- a/ios/MullvadVPN/RelaySelector.swift
+++ b/ios/MullvadVPN/RelaySelector.swift
@@ -13,6 +13,7 @@ struct RelaySelectorResult {
var relay: RelayList.Hostname
var tunnel: RelayList.WireguardTunnel
var endpoint: MullvadEndpoint
+ var geoLocation: GeoLocation
}
struct RelaySelector {
@@ -98,29 +99,47 @@ struct RelaySelector {
func evaluate(with constraints: RelayConstraints) -> RelaySelectorResult? {
let filteredRelayList = applyConstraints(constraints)
- guard let randomRelay = filteredRelayList.countries.randomElement()?
- .cities.randomElement()?
- .relays.randomElement() else {
+ guard let country = filteredRelayList.countries.randomElement() else {
+ return nil
+ }
+
+ guard let city = country.cities.randomElement() else {
+ return nil
+ }
+
+ guard let relay = city.relays.randomElement() else {
return nil
}
- guard let randomTunnel = randomRelay.tunnels?.wireguard?.randomElement() else {
+ guard let tunnel = relay.tunnels?.wireguard?.randomElement() else {
return nil
}
- guard let randomPort = randomTunnel.portRanges.randomElement()?.randomElement() else {
+ guard let port = tunnel.portRanges.randomElement()?.randomElement() else {
return nil
}
let endpoint = MullvadEndpoint(
- ipv4Relay: IPv4Endpoint(ip: randomRelay.ipv4AddrIn, port: randomPort),
+ ipv4Relay: IPv4Endpoint(ip: relay.ipv4AddrIn, port: port),
ipv6Relay: nil,
- ipv4Gateway: randomTunnel.ipv4Gateway,
- ipv6Gateway: randomTunnel.ipv6Gateway,
- publicKey: randomTunnel.publicKey
+ ipv4Gateway: tunnel.ipv4Gateway,
+ ipv6Gateway: tunnel.ipv6Gateway,
+ publicKey: tunnel.publicKey
+ )
+
+ let geoLocation = GeoLocation(
+ country: country.name,
+ city: city.name,
+ latitude: city.latitude,
+ longitude: city.longitude
)
- return RelaySelectorResult(relay: randomRelay, tunnel: randomTunnel, endpoint: endpoint)
+ return RelaySelectorResult(
+ relay: relay,
+ tunnel: tunnel,
+ endpoint: endpoint,
+ geoLocation: geoLocation
+ )
}
}
diff --git a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift
index bbbfa5a7a2..5bfd0cc859 100644
--- a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift
@@ -23,7 +23,14 @@ class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
self.connectionInfo = TunnelConnectionInfo(
ipv4Relay: IPv4Endpoint(ip: IPv4Address("1.2.3.4")!, port: 53),
ipv6Relay: nil,
- hostname: "se7-wireguard")
+ hostname: "se7-wireguard",
+ geoLocation: GeoLocation(
+ country: "Sweden",
+ city: "Stockholm",
+ latitude: 59.3289,
+ longitude: 18.0649
+ )
+ )
completionHandler(nil)
}
diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift
index 1aceba8a0f..1b5dddb281 100644
--- a/ios/PacketTunnel/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider.swift
@@ -289,7 +289,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
self.connectionInfo = TunnelConnectionInfo(
ipv4Relay: selectorResult.endpoint.ipv4Relay,
ipv6Relay: selectorResult.endpoint.ipv6Relay,
- hostname: selectorResult.relay.hostname)
+ hostname: selectorResult.relay.hostname,
+ geoLocation: selectorResult.geoLocation
+ )
os_log(.default, log: tunnelProviderLog,
"Selected relay: %{public}s",