diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-01-07 14:15:15 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-01-08 15:25:05 +0100 |
| commit | d8dea90147e543a3333565f53e9bb8dd92cf6f44 (patch) | |
| tree | ad69aabb691770ffcb1d6c4e46417e1a99f38ca1 | |
| parent | 553b0989fdec943d7f3fc73aa8951c4b70d8e2e9 (diff) | |
| download | mullvadvpn-d8dea90147e543a3333565f53e9bb8dd92cf6f44.tar.xz mullvadvpn-d8dea90147e543a3333565f53e9bb8dd92cf6f44.zip | |
Add Wireguard keys management view
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 24 | ||||
| -rw-r--r-- | ios/MullvadVPN/Base.lproj/Main.storyboard | 305 | ||||
| -rw-r--r-- | ios/MullvadVPN/SegueIdentifier.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsBasicCell.swift | 13 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsViewController.swift | 15 | ||||
| -rw-r--r-- | ios/MullvadVPN/WireguardKeysViewController.swift | 284 |
6 files changed, 617 insertions, 28 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 604eb78c2d..49a7d0a18c 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 58723E7522A54CB2009837F5 /* libwg-go.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58723E7422A54C63009837F5 /* libwg-go.a */; }; 5873884D239E6D7E00E96C4E /* EmbeddedViewContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5873884C239E6D7E00E96C4E /* EmbeddedViewContainerView.swift */; }; 587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587425C02299833500CA2045 /* RootContainerViewController.swift */; }; + 5877152E23981C5B001F8237 /* SettingsBasicCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5877152D23981C5B001F8237 /* SettingsBasicCell.swift */; }; + 5877153023981F7B001F8237 /* WireguardKeysViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */; }; 58781CC922AE7CA8009B9D8E /* RelayConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */; }; 58781CCE22AE8918009B9D8E /* RelayConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */; }; 58781CD522AFBA39009B9D8E /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CD422AFBA39009B9D8E /* RelaySelector.swift */; }; @@ -170,6 +172,8 @@ 58723E7422A54C63009837F5 /* libwg-go.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5873884C239E6D7E00E96C4E /* EmbeddedViewContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedViewContainerView.swift; sourceTree = "<group>"; }; 587425C02299833500CA2045 /* RootContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootContainerViewController.swift; sourceTree = "<group>"; }; + 5877152D23981C5B001F8237 /* SettingsBasicCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBasicCell.swift; sourceTree = "<group>"; }; + 5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireguardKeysViewController.swift; sourceTree = "<group>"; }; 58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConstraints.swift; sourceTree = "<group>"; }; 58781CD422AFBA39009B9D8E /* RelaySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelector.swift; sourceTree = "<group>"; }; 587AD7C523421D7000E93A53 /* TunnelConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelConfiguration.swift; sourceTree = "<group>"; }; @@ -343,6 +347,7 @@ 5888AD86227B17950051EB06 /* SelectLocationController.swift */, 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */, 581CBCEB2298041B00727D7F /* SettingsAppVersionCell.swift */, + 5877152D23981C5B001F8237 /* SettingsBasicCell.swift */, 582BB1AE229566420055B6EF /* SettingsCell.swift */, 58CCA01122424D11004F3011 /* SettingsViewController.swift */, 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */, @@ -359,6 +364,7 @@ 581CBCE52296B97300727D7F /* ViewControllerIdentifier.swift */, 587B08E1229460C1000E6F17 /* WebLinks.swift */, 58B8743122B25A7600015324 /* WireguardAssociatedAddresses.swift */, + 5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */, 58C6B35322BB87C4003C19AD /* WireguardPrivateKey.swift */, ); path = MullvadVPN; @@ -613,6 +619,7 @@ 581CBCE62296B97300727D7F /* ViewControllerIdentifier.swift in Sources */, 58BFA5C622A7C97F00A6173D /* RelayCache.swift in Sources */, 582BB1B1229569620055B6EF /* CustomNavigationBar.swift in Sources */, + 5877153023981F7B001F8237 /* WireguardKeysViewController.swift in Sources */, 58C6B35422BB87C4003C19AD /* WireguardPrivateKey.swift in Sources */, 5888AD87227B17950051EB06 /* SelectLocationController.swift in Sources */, 58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */, @@ -637,6 +644,7 @@ 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */, 5888AD83227B11080051EB06 /* SelectLocationCell.swift in Sources */, 58CE5E66224146200008646E /* LoginViewController.swift in Sources */, + 5877152E23981C5B001F8237 /* SettingsBasicCell.swift in Sources */, 5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */, 58ADDB3C227B1BD200FAFEA7 /* JsonRpc.swift in Sources */, 581CBCEE229826FD00727D7F /* StaticTableViewDataSource.swift in Sources */, @@ -899,7 +907,7 @@ CODE_SIGN_ENTITLEMENTS = MullvadVPN/MullvadVPN.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = G7CDBEG477; INFOPLIST_FILE = MullvadVPN/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -907,7 +915,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2019.10; + MARKETING_VERSION = 2020.1; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -927,7 +935,7 @@ CODE_SIGN_ENTITLEMENTS = MullvadVPN/MullvadVPN.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = G7CDBEG477; INFOPLIST_FILE = MullvadVPN/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -935,7 +943,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2019.10; + MARKETING_VERSION = 2020.1; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -951,7 +959,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = G7CDBEG477; ENABLE_BITCODE = NO; INFOPLIST_FILE = PacketTunnel/Info.plist; @@ -960,7 +968,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2019.10; + MARKETING_VERSION = 2020.1; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -977,7 +985,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = G7CDBEG477; ENABLE_BITCODE = NO; INFOPLIST_FILE = PacketTunnel/Info.plist; @@ -986,7 +994,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2019.10; + MARKETING_VERSION = 2020.1; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/ios/MullvadVPN/Base.lproj/Main.storyboard b/ios/MullvadVPN/Base.lproj/Main.storyboard index b1c99c9877..199b69e272 100644 --- a/ios/MullvadVPN/Base.lproj/Main.storyboard +++ b/ios/MullvadVPN/Base.lproj/Main.storyboard @@ -104,7 +104,7 @@ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <subviews> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pID-oa-Rrg" customClass="SpinnerActivityIndicatorView" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="163.5" y="172.5" width="48" height="48"/> + <rect key="frame" x="163.5" y="173" width="48" height="48"/> <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <constraints> <constraint firstAttribute="height" constant="48" id="2J4-Qc-ctc"/> @@ -112,10 +112,10 @@ </constraints> </view> <imageView clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.0" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="IconSuccess" translatesAutoresizingMaskIntoConstraints="NO" id="7ux-Tb-Fzq"> - <rect key="frame" x="157.5" y="166.5" width="60" height="60"/> + <rect key="frame" x="127.5" y="137" width="120" height="120"/> </imageView> <view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="V3j-Lb-fSQ" userLabel="Form"> - <rect key="frame" x="0.0" y="250.5" width="375" height="126"/> + <rect key="frame" x="0.0" y="251" width="375" height="125.5"/> <subviews> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Login" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Nxn-Fc-EGe"> <rect key="frame" x="24" y="0.0" width="327" height="39"/> @@ -130,7 +130,7 @@ <nil key="highlightedColor"/> </label> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="VmT-ya-ufe" customClass="AccountInputGroupView" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="24" y="78" width="327" height="48"/> + <rect key="frame" x="24" y="77.5" width="327" height="48"/> <subviews> <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="0000 0000 0000 0000" textAlignment="natural" adjustsFontSizeToFit="NO" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="XOB-ct-yLU" userLabel="Account Text Field" customClass="AccountTextField" customModule="MullvadVPN" customModuleProvider="target"> <rect key="frame" x="0.0" y="0.0" width="327" height="48"/> @@ -331,20 +331,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"/> + <rect key="frame" x="0.0" y="55.5" width="375" height="43.5"/> <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"/> + <rect key="frame" x="0.0" y="0.0" width="348" height="43.5"/> <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"/> + <rect key="frame" x="16" y="11" width="63.5" height="21.5"/> <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"/> + <rect key="frame" x="259" y="11" width="81" height="21.5"/> <fontDescription key="fontDescription" type="system" weight="medium" pointSize="13"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> @@ -368,20 +368,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="98.5" width="375" height="43"/> + <rect key="frame" x="0.0" y="99" width="375" height="43.5"/> <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"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/> <autoresizingMask key="autoresizingMask"/> <subviews> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="App version" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pYC-Zb-8N9"> - <rect key="frame" x="16" y="11" width="91" height="21"/> + <rect key="frame" x="16" y="11" width="91" height="21.5"/> <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"/> + <rect key="frame" x="316.5" y="11" width="42.5" height="21.5"/> <fontDescription key="fontDescription" type="system" weight="medium" pointSize="13"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> @@ -402,6 +402,32 @@ <outlet property="versionLabel" destination="sOr-vj-cg7" id="xgH-No-26f"/> </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"/> + <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"/> + <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"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + </subviews> + <constraints> + <constraint firstItem="Amw-A3-ePS" firstAttribute="leading" secondItem="Drq-vk-8F2" secondAttribute="leadingMargin" id="4Ug-US-l8D"/> + <constraint firstItem="Amw-A3-ePS" firstAttribute="top" secondItem="Drq-vk-8F2" secondAttribute="topMargin" id="6ID-BF-Jsp"/> + <constraint firstAttribute="bottomMargin" secondItem="Amw-A3-ePS" secondAttribute="bottom" id="JPQ-S7-zHO"/> + <constraint firstAttribute="trailingMargin" secondItem="Amw-A3-ePS" secondAttribute="trailing" id="lhc-g5-1mb"/> + </constraints> + </tableViewCellContentView> + <color key="backgroundColor" name="Primary"/> + <connections> + <outlet property="titleLabel" destination="Amw-A3-ePS" id="cGS-cX-LXr"/> + </connections> + </tableViewCell> </prototypes> <sections/> <connections> @@ -418,6 +444,7 @@ </navigationItem> <connections> <outlet property="staticDataSource" destination="9xf-6a-8vR" id="E8j-Z4-Ljk"/> + <segue destination="vAK-MJ-h3c" kind="show" identifier="ShowWireguardKeys" id="eDY-gl-kvu"/> </connections> </tableViewController> <placeholder placeholderIdentifier="IBFirstResponder" id="sR5-ix-4x7" userLabel="First Responder" sceneMemberID="firstResponder"/> @@ -429,6 +456,246 @@ </objects> <point key="canvasLocation" x="1690" y="-832"/> </scene> + <!--WireGuard key--> + <scene sceneID="fcg-jd-GmS"> + <objects> + <viewController id="vAK-MJ-h3c" customClass="WireguardKeysViewController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController"> + <view key="view" contentMode="scaleToFill" id="NHk-FR-Mwy"> + <rect key="frame" x="0.0" y="0.0" width="375" height="647"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fa2-zl-Fc4"> + <rect key="frame" x="0.0" y="0.0" width="375" height="647"/> + <subviews> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5VO-oQ-4jM" userLabel="Container"> + <rect key="frame" x="0.0" y="0.0" width="375" height="361"/> + <subviews> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Lx5-tV-hNL" userLabel="Content"> + <rect key="frame" x="24" y="24" width="327" height="313"/> + <subviews> + <view contentMode="scaleToFill" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="xch-VD-kOQ" userLabel="Account number"> + <rect key="frame" x="0.0" y="0.0" width="327" height="45.5"/> + <subviews> + <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="CtE-XF-2uJ"> + <rect key="frame" x="0.0" y="0.0" width="327" height="45.5"/> + <subviews> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="29J-lm-A4D"> + <rect key="frame" x="0.0" y="0.0" width="327" height="17"/> + <subviews> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="751" text="Public key" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="riQ-cz-0de"> + <rect key="frame" x="0.0" y="0.0" width="66" height="17"/> + <fontDescription key="fontDescription" type="system" pointSize="14"/> + <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Z76-2G-dkn" customClass="EmbeddedViewContainerView" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="185" y="0.0" width="142" height="17"/> + <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <constraints> + <constraint firstAttribute="width" constant="142" placeholder="YES" id="v4m-dU-J71"/> + </constraints> + <connections> + <outlet property="embeddedView" destination="1Ue-pb-GCu" id="uUI-08-4yA"/> + </connections> + </view> + </subviews> + <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <constraints> + <constraint firstAttribute="bottom" secondItem="Z76-2G-dkn" secondAttribute="bottom" id="I4r-uc-kgH"/> + <constraint firstItem="riQ-cz-0de" firstAttribute="leading" secondItem="29J-lm-A4D" secondAttribute="leading" id="TZy-KN-DUR"/> + <constraint firstAttribute="trailing" secondItem="Z76-2G-dkn" secondAttribute="trailing" id="cto-xJ-P12"/> + <constraint firstItem="Z76-2G-dkn" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="riQ-cz-0de" secondAttribute="trailing" constant="8" id="kUO-SM-ut1"/> + <constraint firstItem="riQ-cz-0de" firstAttribute="top" secondItem="29J-lm-A4D" secondAttribute="top" id="pef-9R-fZa"/> + <constraint firstAttribute="bottom" secondItem="riQ-cz-0de" secondAttribute="bottom" id="sTe-ay-g1z"/> + <constraint firstItem="Z76-2G-dkn" firstAttribute="top" secondItem="29J-lm-A4D" secondAttribute="top" id="w1K-nl-2QW"/> + </constraints> + </view> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="123456789" textAlignment="natural" lineBreakMode="characterWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UF1-4s-m1S"> + <rect key="frame" x="0.0" y="25" width="327" height="20.5"/> + <fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/> + <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + </subviews> + </stackView> + </subviews> + <constraints> + <constraint firstItem="CtE-XF-2uJ" firstAttribute="top" secondItem="xch-VD-kOQ" secondAttribute="top" id="9pN-dP-gTH"/> + <constraint firstAttribute="trailing" secondItem="CtE-XF-2uJ" secondAttribute="trailing" id="Kjq-dh-nxS"/> + <constraint firstAttribute="bottom" secondItem="CtE-XF-2uJ" secondAttribute="bottom" id="coJ-q9-t0B"/> + <constraint firstItem="CtE-XF-2uJ" firstAttribute="leading" secondItem="xch-VD-kOQ" secondAttribute="leading" id="wHk-cr-fV2"/> + </constraints> + </view> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Bjs-D6-NVj" userLabel="Expiry"> + <rect key="frame" x="0.0" y="69.5" width="327" height="45.5"/> + <subviews> + <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="8f3-SD-t3K"> + <rect key="frame" x="0.0" y="0.0" width="327" height="45.5"/> + <subviews> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Key generated" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FMt-bt-4gy"> + <rect key="frame" x="0.0" y="0.0" width="327" height="17"/> + <fontDescription key="fontDescription" type="system" pointSize="14"/> + <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="6 days ago" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CvU-pV-ixr"> + <rect key="frame" x="0.0" y="25" width="327" height="20.5"/> + <fontDescription key="fontDescription" type="system" weight="medium" pointSize="17"/> + <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <nil key="highlightedColor"/> + </label> + </subviews> + </stackView> + </subviews> + <constraints> + <constraint firstAttribute="bottom" secondItem="8f3-SD-t3K" secondAttribute="bottom" id="Lou-NI-gF9"/> + <constraint firstAttribute="trailing" secondItem="8f3-SD-t3K" secondAttribute="trailing" id="Xok-g3-2S0"/> + <constraint firstItem="8f3-SD-t3K" firstAttribute="top" secondItem="Bjs-D6-NVj" secondAttribute="top" id="asr-RD-H6w"/> + <constraint firstItem="8f3-SD-t3K" firstAttribute="leading" secondItem="Bjs-D6-NVj" secondAttribute="leading" id="fvj-uI-LRp"/> + </constraints> + </view> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ydQ-IP-KZb" userLabel="Buttons"> + <rect key="frame" x="0.0" y="139" width="327" height="174"/> + <subviews> + <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="zF0-5W-t7M"> + <rect key="frame" x="0.0" y="0.0" width="327" height="174"/> + <subviews> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OCa-Jz-b7W" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="0.0" y="0.0" width="327" height="42"/> + <constraints> + <constraint firstAttribute="height" constant="42" placeholder="YES" id="IpC-KC-l52"/> + </constraints> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <state key="normal" title="Regenerate key" backgroundImage="SuccessButton"/> + <connections> + <action selector="handleRegenerateKey:" destination="vAK-MJ-h3c" eventType="touchUpInside" id="s39-bg-vkG"/> + </connections> + </button> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qEF-8w-MdR" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="0.0" y="66" width="327" height="42"/> + <constraints> + <constraint firstAttribute="height" constant="42" placeholder="YES" id="299-Lu-yIB"/> + </constraints> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <state key="normal" title="Verify key" backgroundImage="SuccessButton"/> + <connections> + <action selector="handleVerifyKey:" destination="vAK-MJ-h3c" eventType="touchUpInside" id="wGf-5k-Zw2"/> + </connections> + </button> + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4U5-oz-pbv" customClass="CustomButton" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="0.0" y="132" width="327" height="42"/> + <constraints> + <constraint firstAttribute="height" constant="42" placeholder="YES" id="eTr-Qz-83C"/> + </constraints> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <state key="normal" title="Manage keys" image="IconExtlink" backgroundImage="SuccessButton"/> + <connections> + <action selector="handleManageKeys:" destination="vAK-MJ-h3c" eventType="touchUpInside" id="DDg-kZ-9k6"/> + </connections> + </button> + </subviews> + </stackView> + </subviews> + <constraints> + <constraint firstAttribute="bottom" secondItem="zF0-5W-t7M" secondAttribute="bottom" id="Hb8-OT-rIo"/> + <constraint firstAttribute="trailing" secondItem="zF0-5W-t7M" secondAttribute="trailing" id="bO9-9j-LGw"/> + <constraint firstItem="zF0-5W-t7M" firstAttribute="leading" secondItem="ydQ-IP-KZb" secondAttribute="leading" id="d8z-bR-Gjl"/> + <constraint firstItem="zF0-5W-t7M" firstAttribute="top" secondItem="ydQ-IP-KZb" secondAttribute="top" id="ylb-xz-fnV"/> + </constraints> + </view> + </subviews> + <constraints> + <constraint firstItem="ydQ-IP-KZb" firstAttribute="top" secondItem="Bjs-D6-NVj" secondAttribute="bottom" constant="24" id="37w-fI-zi8"/> + <constraint firstItem="Bjs-D6-NVj" firstAttribute="top" secondItem="xch-VD-kOQ" secondAttribute="bottom" constant="24" id="8F9-gx-ngL"/> + <constraint firstAttribute="trailing" secondItem="xch-VD-kOQ" secondAttribute="trailing" id="GLB-vv-DDi"/> + <constraint firstAttribute="trailing" secondItem="Bjs-D6-NVj" secondAttribute="trailing" id="Hpo-go-tU4"/> + <constraint firstAttribute="trailing" secondItem="ydQ-IP-KZb" secondAttribute="trailing" id="OwJ-Xb-q8O"/> + <constraint firstItem="ydQ-IP-KZb" firstAttribute="leading" secondItem="Lx5-tV-hNL" secondAttribute="leading" id="P69-3O-u0S"/> + <constraint firstItem="Bjs-D6-NVj" firstAttribute="leading" secondItem="Lx5-tV-hNL" secondAttribute="leading" id="Qj6-44-14Q"/> + <constraint firstItem="xch-VD-kOQ" firstAttribute="leading" secondItem="Lx5-tV-hNL" secondAttribute="leading" id="Y3H-6z-JyF"/> + <constraint firstAttribute="bottom" secondItem="ydQ-IP-KZb" secondAttribute="bottom" id="aRW-Pg-yg6"/> + <constraint firstItem="xch-VD-kOQ" firstAttribute="top" secondItem="Lx5-tV-hNL" secondAttribute="top" id="i3j-2f-8vV"/> + </constraints> + </view> + </subviews> + <constraints> + <constraint firstAttribute="bottom" secondItem="Lx5-tV-hNL" secondAttribute="bottom" constant="24" id="1sW-Lm-fPs"/> + <constraint firstItem="Lx5-tV-hNL" firstAttribute="top" secondItem="5VO-oQ-4jM" secondAttribute="top" constant="24" id="6F1-G5-kJW"/> + <constraint firstAttribute="trailing" secondItem="Lx5-tV-hNL" secondAttribute="trailing" constant="24" id="YZH-zj-zvM"/> + <constraint firstItem="Lx5-tV-hNL" firstAttribute="leading" secondItem="5VO-oQ-4jM" secondAttribute="leading" constant="24" id="rHz-2D-i6n"/> + </constraints> + </view> + </subviews> + <constraints> + <constraint firstItem="5VO-oQ-4jM" firstAttribute="width" secondItem="fa2-zl-Fc4" secondAttribute="width" id="86a-VC-VLn"/> + <constraint firstAttribute="bottom" secondItem="5VO-oQ-4jM" secondAttribute="bottom" id="ERb-9i-nFf"/> + <constraint firstItem="5VO-oQ-4jM" firstAttribute="leading" secondItem="fa2-zl-Fc4" secondAttribute="leading" id="KdL-EB-Idq"/> + <constraint firstItem="5VO-oQ-4jM" firstAttribute="top" secondItem="fa2-zl-Fc4" secondAttribute="top" id="avu-mu-m90"/> + <constraint firstAttribute="trailing" secondItem="5VO-oQ-4jM" secondAttribute="trailing" id="sUD-Wq-7oa"/> + </constraints> + </scrollView> + </subviews> + <color key="backgroundColor" name="Secondary"/> + <constraints> + <constraint firstItem="fa2-zl-Fc4" firstAttribute="top" secondItem="NHk-FR-Mwy" secondAttribute="top" id="0XS-W3-vV4"/> + <constraint firstItem="fa2-zl-Fc4" firstAttribute="bottom" secondItem="uHO-zG-HAA" secondAttribute="bottom" id="3Uo-OT-Y0q"/> + <constraint firstItem="fa2-zl-Fc4" firstAttribute="leading" secondItem="uHO-zG-HAA" secondAttribute="leading" id="4rW-4C-pRs"/> + <constraint firstItem="fa2-zl-Fc4" firstAttribute="trailing" secondItem="uHO-zG-HAA" secondAttribute="trailing" id="VuC-Wb-6U4"/> + </constraints> + <viewLayoutGuide key="safeArea" id="uHO-zG-HAA"/> + </view> + <navigationItem key="navigationItem" title="WireGuard key" id="6ve-v7-tYQ"/> + <connections> + <outlet property="creationDateLabel" destination="CvU-pV-ixr" id="qSg-Be-sO0"/> + <outlet property="publicKeyLabel" destination="UF1-4s-m1S" id="PaX-IU-VEH"/> + <outlet property="regenerateKeyButton" destination="OCa-Jz-b7W" id="BZD-Qc-8lO"/> + <outlet property="verifyKeyButton" destination="qEF-8w-MdR" id="zuU-Ts-QNG"/> + <outlet property="wireguardKeyStatusView" destination="1Ue-pb-GCu" id="VV8-bG-SgD"/> + </connections> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="WDa-g6-tXg" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> + <view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="1Ue-pb-GCu" customClass="WireguardKeyStatusView" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="0.0" y="0.0" width="240" height="16"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <subviews> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="751" text="Key is valid" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ocb-Gs-xuo"> + <rect key="frame" x="0.0" y="0.0" width="240" height="16"/> + <fontDescription key="fontDescription" type="system" pointSize="14"/> + <color key="textColor" name="Success"/> + <nil key="highlightedColor"/> + </label> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ZN9-r7-vKB" customClass="SpinnerActivityIndicatorView" customModule="MullvadVPN" customModuleProvider="target"> + <rect key="frame" x="224" y="0.0" width="16" height="16"/> + <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <constraints> + <constraint firstAttribute="width" secondItem="ZN9-r7-vKB" secondAttribute="height" multiplier="1:1" id="7dm-3X-rB1"/> + </constraints> + <userDefinedRuntimeAttributes> + <userDefinedRuntimeAttribute type="number" keyPath="thickness"> + <real key="value" value="2"/> + </userDefinedRuntimeAttribute> + </userDefinedRuntimeAttributes> + </view> + </subviews> + <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <constraints> + <constraint firstItem="ZN9-r7-vKB" firstAttribute="height" secondItem="Ocb-Gs-xuo" secondAttribute="height" id="0GM-ky-aMe"/> + <constraint firstAttribute="trailing" secondItem="Ocb-Gs-xuo" secondAttribute="trailing" id="C6Q-Ie-omp"/> + <constraint firstItem="Ocb-Gs-xuo" firstAttribute="leading" secondItem="1Ue-pb-GCu" secondAttribute="leading" id="M7j-bw-5Ye"/> + <constraint firstAttribute="trailing" secondItem="ZN9-r7-vKB" secondAttribute="trailing" id="YRs-9L-5CY"/> + <constraint firstItem="ZN9-r7-vKB" firstAttribute="centerY" secondItem="Ocb-Gs-xuo" secondAttribute="centerY" id="d43-KC-IK5"/> + <constraint firstAttribute="bottom" secondItem="Ocb-Gs-xuo" secondAttribute="bottom" id="ivo-8C-Rj8"/> + <constraint firstItem="Ocb-Gs-xuo" firstAttribute="top" secondItem="1Ue-pb-GCu" secondAttribute="top" id="kgV-z9-gxp"/> + </constraints> + <connections> + <outlet property="activityIndicator" destination="ZN9-r7-vKB" id="lj5-xB-7Ra"/> + <outlet property="textLabel" destination="Ocb-Gs-xuo" id="LoV-ls-1BU"/> + </connections> + </view> + </objects> + <point key="canvasLocation" x="2576.8000000000002" y="-513.19340329835086"/> + </scene> <!--Account--> <scene sceneID="Ca0-W1-eLb"> <objects> @@ -592,7 +859,7 @@ <placeholder placeholderIdentifier="IBFirstResponder" id="3tt-67-nI8" userLabel="First Responder" sceneMemberID="firstResponder"/> <exit id="P2i-eG-jQx" userLabel="Exit" sceneMemberID="exit"/> </objects> - <point key="canvasLocation" x="2649" y="-834"/> + <point key="canvasLocation" x="2578" y="-1258"/> </scene> <!--Navigation Controller--> <scene sceneID="er3-W2-NkS"> @@ -663,31 +930,31 @@ </view> <prototypes> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="aFz-H5-sPu" customClass="SelectLocationCell" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="173" width="375" height="43.5"/> + <rect key="frame" x="0.0" y="173" width="375" height="48.5"/> <autoresizingMask key="autoresizingMask"/> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="aFz-H5-sPu" id="6nQ-gT-vzf"> - <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="48.5"/> <autoresizingMask key="autoresizingMask"/> <subviews> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5ag-N4-pUg" customClass="RelayStatusIndicatorView" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="16" y="14" width="16" height="16"/> + <rect key="frame" x="16" y="16.5" width="16" height="16"/> <constraints> <constraint firstAttribute="height" constant="16" id="QWj-hh-I3P"/> <constraint firstAttribute="width" constant="16" id="TFV-yi-LXG"/> </constraints> </view> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="y7o-0b-MUV"> - <rect key="frame" x="44" y="11" width="42" height="21.5"/> + <rect key="frame" x="44" y="11" width="42" height="26.5"/> <fontDescription key="fontDescription" type="system" pointSize="17"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> </label> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="IconTick" translatesAutoresizingMaskIntoConstraints="NO" id="e1o-Bl-zd5"> - <rect key="frame" x="12" y="10" width="24" height="24"/> + <rect key="frame" x="0.0" y="0.5" width="48" height="48"/> <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> </imageView> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KaW-bN-I51"> - <rect key="frame" x="311" y="0.0" width="64" height="43.5"/> + <rect key="frame" x="311" y="0.0" width="64" height="48.5"/> <constraints> <constraint firstAttribute="width" constant="64" id="UU3-Di-65E"/> </constraints> diff --git a/ios/MullvadVPN/SegueIdentifier.swift b/ios/MullvadVPN/SegueIdentifier.swift index eaa8a10cbb..84f245fdff 100644 --- a/ios/MullvadVPN/SegueIdentifier.swift +++ b/ios/MullvadVPN/SegueIdentifier.swift @@ -21,6 +21,10 @@ extension SegueIdentifier { case showConnect = "ShowConnect" } + enum Settings: String, SegueConvertible { + case showWireguardKeys = "ShowWireguardKeys" + } + enum Connect: String, SegueConvertible { case embedTunnelControls = "EmbedTunnelControls" case showRelaySelector = "ShowRelaySelector" diff --git a/ios/MullvadVPN/SettingsBasicCell.swift b/ios/MullvadVPN/SettingsBasicCell.swift new file mode 100644 index 0000000000..a4e98a58eb --- /dev/null +++ b/ios/MullvadVPN/SettingsBasicCell.swift @@ -0,0 +1,13 @@ +// +// SettingsBasicCell.swift +// MullvadVPN +// +// Created by pronebird on 04/12/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import UIKit + +class SettingsBasicCell: SettingsCell { + @IBOutlet var titleLabel: UILabel! +} diff --git a/ios/MullvadVPN/SettingsViewController.swift b/ios/MullvadVPN/SettingsViewController.swift index fe6b11743d..4c8cc11c20 100644 --- a/ios/MullvadVPN/SettingsViewController.swift +++ b/ios/MullvadVPN/SettingsViewController.swift @@ -16,6 +16,7 @@ class SettingsViewController: UITableViewController { private enum CellIdentifier: String { case account = "Account" case appVersion = "AppVersion" + case basicDisclosure = "BasicDisclosure" } private weak var accountRow: StaticTableViewRow? @@ -43,9 +44,21 @@ class SettingsViewController: UITableViewController { cell.accountExpiryDate = Account.shared.expiry } + let wireguardKeyRow = StaticTableViewRow(reuseIdentifier: CellIdentifier.basicDisclosure.rawValue) { (_, cell) in + let cell = cell as! SettingsBasicCell + + cell.titleLabel.text = NSLocalizedString("WireGuard key", comment: "") + } + + wireguardKeyRow.actionBlock = { [weak self] (indexPath) in + self?.performSegue( + withIdentifier: SegueIdentifier.Settings.showWireguardKeys.rawValue, + sender: nil) + } + self.accountRow = accountRow - topSection.addRows([accountRow]) + topSection.addRows([accountRow, wireguardKeyRow]) staticDataSource.addSections([topSection]) } diff --git a/ios/MullvadVPN/WireguardKeysViewController.swift b/ios/MullvadVPN/WireguardKeysViewController.swift new file mode 100644 index 0000000000..9024f82627 --- /dev/null +++ b/ios/MullvadVPN/WireguardKeysViewController.swift @@ -0,0 +1,284 @@ +// +// WireguardKeysViewController.swift +// MullvadVPN +// +// Created by pronebird on 04/12/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import Combine +import Foundation +import UIKit +import os + +/// A UI refresh interval for the public key creation date (in seconds) +private let kCreationDateRefreshInterval = TimeInterval(60) + +private enum WireguardKeysViewState { + case `default` + case verifyingKey + case verifiedKey(Bool) + case regeneratingKey +} + +enum VerifyWireguardPublicKeyError { + case network(MullvadAPI.Error) + case server(MullvadAPI.ResponseError) +} + +extension VerifyWireguardPublicKeyError: LocalizedError { + var errorDescription: String? { + return NSLocalizedString("Cannot verify the public key", comment: "") + } + + var failureReason: String? { + switch self { + case .network(.network(let urlError)): + return urlError.localizedDescription + case .server(let serverError): + return serverError.errorDescription + default: + return NSLocalizedString("Internal error", comment: "") + } + } +} + +class WireguardKeysViewController: UIViewController { + + @IBOutlet var publicKeyLabel: UILabel! + @IBOutlet var creationDateLabel: UILabel! + @IBOutlet var regenerateKeyButton: UIButton! + @IBOutlet var verifyKeyButton: UIButton! + @IBOutlet var wireguardKeyStatusView: WireguardKeyStatusView! + + private var fetchKeySubscriber: AnyCancellable? + private var verifyKeySubscriber: AnyCancellable? + private var regenerateKeySubscriber: AnyCancellable? + private var timerSubscriber: AnyCancellable? + + private let apiClient = MullvadAPI() + private var publicKey: WireguardPublicKey? + + private var state: WireguardKeysViewState = .default { + didSet { + updateViewState(state) + } + } + + private lazy var relativeFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .full + formatter.allowedUnits = [.minute, .hour, .day, .month, .year] + formatter.maximumUnitCount = 1 + + return formatter + }() + + override func viewDidLoad() { + super.viewDidLoad() + + // Reset Storyboard placeholders + publicKeyLabel.text = "-" + creationDateLabel.text = "-" + + timerSubscriber = Timer.publish(every: kCreationDateRefreshInterval, on: .main, in: .common) + .autoconnect() + .sink { [weak self] _ in + guard let self = self else { return } + + if let creationDate = self.publicKey?.creationDate { + self.updateCreationDateLabel(with: creationDate) + } + } + + loadPublicKey() + } + + // MARK: - IBActions + + @IBAction func handleRegenerateKey(_ sender: Any) { + regeneratePrivateKey() + } + + @IBAction func handleVerifyKey(_ sender: Any) { + guard let accountToken = Account.shared.token, + let publicKey = publicKey else { return } + + verifyKey(accountToken: accountToken, publicKey: publicKey) + } + + @IBAction func handleManageKeys(_ sender: Any) { + UIApplication.shared.open(WebLinks.manageKeysURL, options: [:]) + } + + // MARK: - Private + + private func formatKeyGenerationElapsedTime(with creationDate: Date) -> String? { + let elapsedTime = Date().timeIntervalSince(creationDate) + + if elapsedTime >= 60 { + if let formattedInterval = relativeFormatter.string(from: elapsedTime) { + return String.localizedStringWithFormat( + NSLocalizedString("%@ ago", comment: ""), + formattedInterval) + } else { + return nil + } + } else { + return NSLocalizedString("Less than a minute ago", comment: "") + } + } + + private func updateCreationDateLabel(with creationDate: Date) { + creationDateLabel.text = formatKeyGenerationElapsedTime(with: creationDate) ?? "-" + } + + private func loadPublicKey() { + fetchKeySubscriber = TunnelManager.shared.getWireguardPublicKey() + .receive(on: DispatchQueue.main) + .sink(receiveCompletion: { (completion) in + switch completion { + case .finished: + break + + case .failure(let error): + os_log(.error, "Failed to receive the public key for Wireguard: %{public}s", + error.localizedDescription) + + self.presentError(error, preferredStyle: .alert) + } + }) { [weak self] (publicKey) in + guard let self = self else { return } + + self.publicKeyLabel.text = publicKey.rawRepresentation.base64EncodedString() + self.updateCreationDateLabel(with: publicKey.creationDate) + + self.publicKey = publicKey + } + } + + private func updateViewState(_ state: WireguardKeysViewState) { + switch state { + case .default: + setKeyActionButtonsEnabled(true) + wireguardKeyStatusView.status = .default + + case .verifyingKey: + setKeyActionButtonsEnabled(false) + wireguardKeyStatusView.status = .verifying + + case .verifiedKey(let isValid): + setKeyActionButtonsEnabled(true) + wireguardKeyStatusView.status = .verified(isValid) + + case .regeneratingKey: + setKeyActionButtonsEnabled(false) + wireguardKeyStatusView.status = .verifying + } + } + + private func setKeyActionButtonsEnabled(_ enabled: Bool) { + regenerateKeyButton.isEnabled = enabled + verifyKeyButton.isEnabled = enabled + } + + private func verifyKey(accountToken: String, publicKey: WireguardPublicKey) { + verifyKeySubscriber = apiClient.checkWireguardKey( + accountToken: accountToken, + publicKey: publicKey.rawRepresentation + ) + .retry(1) + .receive(on: DispatchQueue.main) + .mapError { VerifyWireguardPublicKeyError.network($0) } + .flatMap({ (response) in + response.result + .mapError { VerifyWireguardPublicKeyError.server($0) } + .publisher + }) + .handleEvents(receiveSubscription: { _ in + self.updateViewState(.verifyingKey) + }) + .sink(receiveCompletion: { (completion) in + switch completion { + case .finished: + break + + case .failure(let error): + self.presentError(error, preferredStyle: .alert) + self.updateViewState(.default) + } + }) { (isValid) in + self.updateViewState(.verifiedKey(isValid)) + } + } + + private func regeneratePrivateKey() { + regenerateKeySubscriber = TunnelManager.shared.regeneratePrivateKey() + .receive(on: DispatchQueue.main) + .handleEvents(receiveSubscription: { (_) in + self.updateViewState(.regeneratingKey) + }, receiveCompletion: { (completion) in + self.updateViewState(.default) + }) + .sink { (completion) in + switch completion { + case .finished: + self.loadPublicKey() + + case .failure(let error): + os_log(.error, "Failed to re-generate the private key: %{public}s", + error.errorDescription ?? "") + + self.presentError(error, preferredStyle: .alert) + } + } + } + +} + +class WireguardKeyStatusView: UIView { + + enum Status { + case `default`, verifying, verified(Bool) + } + + @IBOutlet var textLabel: UILabel! + @IBOutlet var activityIndicator: SpinnerActivityIndicatorView! + + var status: Status = .default { + didSet { + updateView() + } + } + + override func awakeFromNib() { + super.awakeFromNib() + + updateView() + } + + private func updateView() { + switch status { + case .default: + textLabel.isHidden = true + activityIndicator.stopAnimating() + + case .verifying: + textLabel.isHidden = true + activityIndicator.startAnimating() + + case .verified(let isValid): + textLabel.isHidden = false + activityIndicator.stopAnimating() + + if isValid { + textLabel.textColor = .successColor + textLabel.text = NSLocalizedString("Key is valid", comment: "") + } else { + textLabel.textColor = .dangerColor + textLabel.text = NSLocalizedString("Key is invalid", comment: "") + } + } + } + +} |
