diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-02-17 16:02:55 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-02-18 13:02:48 +0100 |
| commit | 8cabbf30adedaea8969080804c2e335e0625663b (patch) | |
| tree | 3dd98c99cdf3604e6d79996fc3ce64cae38648de | |
| parent | 354c7feec88797e6696dbbc7a5177187baae3a2e (diff) | |
| download | mullvadvpn-8cabbf30adedaea8969080804c2e335e0625663b.tar.xz mullvadvpn-8cabbf30adedaea8969080804c2e335e0625663b.zip | |
Show relay location and connection info
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 16 | ||||
| -rw-r--r-- | ios/MullvadVPN/Base.lproj/Main.storyboard | 81 | ||||
| -rw-r--r-- | ios/MullvadVPN/ConnectViewController.swift | 33 | ||||
| -rw-r--r-- | ios/MullvadVPN/ConnectionPanelView.swift | 240 | ||||
| -rw-r--r-- | ios/MullvadVPN/GeoLocation.swift | 16 | ||||
| -rw-r--r-- | ios/MullvadVPN/PacketTunnelIpc.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/RelaySelector.swift | 39 | ||||
| -rw-r--r-- | ios/MullvadVPN/SimulatorTunnelProviderHost.swift | 9 | ||||
| -rw-r--r-- | ios/PacketTunnel/PacketTunnelProvider.swift | 4 |
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", |
