diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-07-22 18:35:06 +0300 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-07-23 15:13:24 +0300 |
| commit | 970e46819ba06b1383bd10692d426b4cea0573c8 (patch) | |
| tree | 96ae16f3e33b06cbf7300ce84d63b7affa7158be | |
| parent | 5a3fc686918ed7386be143651363a331bfae6ece (diff) | |
| download | mullvadvpn-970e46819ba06b1383bd10692d426b4cea0573c8.tar.xz mullvadvpn-970e46819ba06b1383bd10692d426b4cea0573c8.zip | |
Move SelectLocationController from Storyboard into code
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 8 | ||||
| -rw-r--r-- | ios/MullvadVPN/Base.lproj/Main.storyboard | 110 | ||||
| -rw-r--r-- | ios/MullvadVPN/ConnectViewController.swift | 63 | ||||
| -rw-r--r-- | ios/MullvadVPN/SegueIdentifier.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/SelectLocationCell.swift | 75 | ||||
| -rw-r--r-- | ios/MullvadVPN/SelectLocationController.swift | 29 | ||||
| -rw-r--r-- | ios/MullvadVPN/SelectLocationHeaderView.swift | 41 | ||||
| -rw-r--r-- | ios/MullvadVPN/SelectLocationNavigationController.swift | 61 |
8 files changed, 223 insertions, 168 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index bb12fd7909..38fda942ac 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -76,6 +76,8 @@ 5857F23D24C8449A00CF6F47 /* TransformOperationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580EE22024B3240100F9D8A1 /* TransformOperationObserver.swift */; }; 5857F23E24C844A000CF6F47 /* OperationBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580EE21424B3231200F9D8A1 /* OperationBlockObserver.swift */; }; 5857F23F24C844AD00CF6F47 /* Locking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BA692D23E99EFF009DC256 /* Locking.swift */; }; + 5857F24324C8662600CF6F47 /* SelectLocationHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5857F24224C8662600CF6F47 /* SelectLocationHeaderView.swift */; }; + 5857F24724C882D700CF6F47 /* SelectLocationNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5857F24624C882D700CF6F47 /* SelectLocationNavigationController.swift */; }; 5860F1C223A785C600CEA666 /* WireguardDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5860F1C123A785C600CEA666 /* WireguardDevice.swift */; }; 5860F1C423A8D25F00CEA666 /* WireguardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5860F1C323A8D25F00CEA666 /* WireguardConfiguration.swift */; }; 5860F1EB23AA4CF300CEA666 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5860F1EA23AA4CF300CEA666 /* Logging.swift */; }; @@ -272,6 +274,8 @@ 5845F841236CBACD00B2D93C /* PacketTunnelIpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelIpc.swift; sourceTree = "<group>"; }; 584B26F3237434D00073B10E /* RelaySelectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorTests.swift; sourceTree = "<group>"; }; 58561C98239A5D1500BD6B5E /* IPEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPEndpoint.swift; sourceTree = "<group>"; }; + 5857F24224C8662600CF6F47 /* SelectLocationHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationHeaderView.swift; sourceTree = "<group>"; }; + 5857F24624C882D700CF6F47 /* SelectLocationNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationNavigationController.swift; sourceTree = "<group>"; }; 5860F1C123A785C600CEA666 /* WireguardDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireguardDevice.swift; sourceTree = "<group>"; }; 5860F1C323A8D25F00CEA666 /* WireguardConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireguardConfiguration.swift; sourceTree = "<group>"; }; 5860F1EA23AA4CF300CEA666 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; }; @@ -541,6 +545,8 @@ 5867A51B2248F26A005513C0 /* SegueIdentifier.swift */, 5888AD82227B11080051EB06 /* SelectLocationCell.swift */, 5888AD86227B17950051EB06 /* SelectLocationController.swift */, + 5857F24224C8662600CF6F47 /* SelectLocationHeaderView.swift */, + 5857F24624C882D700CF6F47 /* SelectLocationNavigationController.swift */, 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */, 581CBCEB2298041B00727D7F /* SettingsAppVersionCell.swift */, 5877152D23981C5B001F8237 /* SettingsBasicCell.swift */, @@ -907,6 +913,7 @@ 580EE21E24B3237F00F9D8A1 /* OutputOperation.swift in Sources */, 5840250122B1124600E4CFEC /* IpAddress+Codable.swift in Sources */, 58EC4E6C23915325003F5C5B /* Bundle+MullvadVersion.swift in Sources */, + 5857F24724C882D700CF6F47 /* SelectLocationNavigationController.swift in Sources */, 580EE21224B322FC00F9D8A1 /* ResultOperation.swift in Sources */, 58BA693123EADA6A009DC256 /* SimulatorTunnelProvider.swift in Sources */, 58E6771F24ADFE7800AA26E7 /* SettingsNavigationController.swift in Sources */, @@ -968,6 +975,7 @@ 581CBCEE229826FD00727D7F /* StaticTableViewDataSource.swift in Sources */, 58CE5E64224146200008646E /* AppDelegate.swift in Sources */, 58C6B35E22BBBFE3003C19AD /* Data+HexCoding.swift in Sources */, + 5857F24324C8662600CF6F47 /* SelectLocationHeaderView.swift in Sources */, 58AEEF652344A36000C9BBD5 /* KeychainError.swift in Sources */, 580EE22824B3289300F9D8A1 /* AssociatedValue.swift in Sources */, 58CCA01222424D11004F3011 /* SettingsViewController.swift in Sources */, diff --git a/ios/MullvadVPN/Base.lproj/Main.storyboard b/ios/MullvadVPN/Base.lproj/Main.storyboard index 164513734b..9cf7ac24e6 100644 --- a/ios/MullvadVPN/Base.lproj/Main.storyboard +++ b/ios/MullvadVPN/Base.lproj/Main.storyboard @@ -1011,114 +1011,6 @@ </objects> <point key="canvasLocation" x="-551" y="27"/> </scene> - <!--Select location--> - <scene sceneID="Kar-Ys-a6u"> - <objects> - <tableViewController storyboardIdentifier="SelectLocation" id="FxZ-7F-3yi" customClass="SelectLocationController" customModule="MullvadVPN" customModuleProvider="target" sceneMemberID="viewController"> - <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="56" sectionHeaderHeight="28" sectionFooterHeight="28" id="LKX-4h-vIx"> - <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <color key="backgroundColor" name="Secondary"/> - <color key="separatorColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> - <view key="tableHeaderView" contentMode="scaleToFill" id="YMi-O0-jT1"> - <rect key="frame" x="0.0" y="0.0" width="375" height="145"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> - <subviews> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="While connected, your real location is masked with a private and secure location in the selected region" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="X0P-N8-lda"> - <rect key="frame" x="24" y="24" width="327" height="97"/> - <fontDescription key="fontDescription" type="system" pointSize="17"/> - <color key="textColor" white="1" alpha="0.60217786815068497" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> - <nil key="highlightedColor"/> - </label> - </subviews> - <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> - <constraints> - <constraint firstAttribute="bottomMargin" secondItem="X0P-N8-lda" secondAttribute="bottom" id="Ghh-mK-nAy"/> - <constraint firstAttribute="trailingMargin" secondItem="X0P-N8-lda" secondAttribute="trailing" id="gRy-Wb-s8K"/> - <constraint firstItem="X0P-N8-lda" firstAttribute="top" secondItem="YMi-O0-jT1" secondAttribute="topMargin" id="mHY-Fb-HcE"/> - <constraint firstItem="X0P-N8-lda" firstAttribute="leading" secondItem="YMi-O0-jT1" secondAttribute="leadingMargin" id="s3I-Rw-1Jg"/> - </constraints> - <edgeInsets key="layoutMargins" top="24" left="24" bottom="24" right="24"/> - </view> - <prototypes> - <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="aFz-H5-sPu" customClass="SelectLocationCell" customModule="MullvadVPN" customModuleProvider="target"> - <rect key="frame" x="0.0" y="173" width="375" height="43.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"/> - <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"/> - <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"/> - <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"/> - <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"/> - <accessibility key="accessibilityConfiguration" identifier="ExpandButton"/> - <constraints> - <constraint firstAttribute="width" constant="64" id="UU3-Di-65E"/> - </constraints> - <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> - <state key="normal" image="IconChevronDown"/> - </button> - </subviews> - <color key="backgroundColor" name="Primary"/> - <constraints> - <constraint firstItem="KaW-bN-I51" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="y7o-0b-MUV" secondAttribute="trailing" id="3uQ-T4-POk"/> - <constraint firstAttribute="bottomMargin" secondItem="y7o-0b-MUV" secondAttribute="bottom" id="7ly-PI-8H3"/> - <constraint firstAttribute="bottom" secondItem="KaW-bN-I51" secondAttribute="bottom" id="9I6-k7-21c"/> - <constraint firstItem="e1o-Bl-zd5" firstAttribute="centerX" secondItem="5ag-N4-pUg" secondAttribute="centerX" id="Bk5-41-u3r"/> - <constraint firstItem="e1o-Bl-zd5" firstAttribute="centerY" secondItem="5ag-N4-pUg" secondAttribute="centerY" id="Pfw-mx-SZ3"/> - <constraint firstAttribute="trailing" secondItem="KaW-bN-I51" secondAttribute="trailing" id="Z2F-pa-wEE"/> - <constraint firstItem="y7o-0b-MUV" firstAttribute="top" secondItem="6nQ-gT-vzf" secondAttribute="topMargin" id="dw6-el-6DC"/> - <constraint firstItem="5ag-N4-pUg" firstAttribute="leading" secondItem="6nQ-gT-vzf" secondAttribute="leadingMargin" id="h2b-0z-IjZ"/> - <constraint firstItem="KaW-bN-I51" firstAttribute="top" secondItem="6nQ-gT-vzf" secondAttribute="top" id="ong-F1-a4V"/> - <constraint firstItem="5ag-N4-pUg" firstAttribute="centerY" secondItem="6nQ-gT-vzf" secondAttribute="centerY" id="upC-Vc-y0Y"/> - <constraint firstItem="y7o-0b-MUV" firstAttribute="leading" secondItem="5ag-N4-pUg" secondAttribute="trailing" constant="12" id="ylV-VK-pUm"/> - </constraints> - </tableViewCellContentView> - <connections> - <outlet property="collapseButton" destination="KaW-bN-I51" id="n5C-yZ-39f"/> - <outlet property="locationLabel" destination="y7o-0b-MUV" id="Pw4-Kb-uCu"/> - <outlet property="statusIndicator" destination="5ag-N4-pUg" id="wXj-KL-JdB"/> - <outlet property="tickImageView" destination="e1o-Bl-zd5" id="UjZ-ct-oPZ"/> - </connections> - </tableViewCell> - </prototypes> - <connections> - <outlet property="delegate" destination="FxZ-7F-3yi" id="yWE-Dc-Wl5"/> - </connections> - </tableView> - <navigationItem key="navigationItem" title="Select location" largeTitleDisplayMode="always" id="PZM-r8-1Sb"> - <barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="4T0-a3-Ce4"> - <connections> - <segue destination="6Lc-ZQ-E4P" kind="unwind" identifier="" unwindAction="unwindFromSelectLocationWithSegue:" id="gAz-uu-Whd"/> - </connections> - </barButtonItem> - </navigationItem> - <simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/> - <connections> - <segue destination="6Lc-ZQ-E4P" kind="unwind" identifier="ReturnToConnectWithNewRelay" unwindAction="unwindFromSelectLocationWithSegue:" id="SPT-Ay-Cy3"/> - </connections> - </tableViewController> - <placeholder placeholderIdentifier="IBFirstResponder" id="EvX-LH-gOg" userLabel="First Responder" sceneMemberID="firstResponder"/> - <exit id="6Lc-ZQ-E4P" userLabel="Exit" sceneMemberID="exit"/> - </objects> - <point key="canvasLocation" x="1689" y="779"/> - </scene> <!--Tunnel Control View Controller--> <scene sceneID="Zzn-S1-fVu"> <objects> @@ -1352,10 +1244,8 @@ <resources> <image name="DangerButton" width="9" height="9"/> <image name="DefaultButton" width="9" height="9"/> - <image name="IconChevronDown" width="24" height="24"/> <image name="IconExtlink" width="16" height="16"/> <image name="IconSuccess" width="60" height="60"/> - <image name="IconTick" width="24" height="24"/> <image name="LogoIcon" width="253" height="253"/> <image name="SuccessButton" width="9" height="9"/> <image name="TranslucentDangerButton" width="9" height="9"/> diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift index ace0eb198b..52b8a87f8c 100644 --- a/ios/MullvadVPN/ConnectViewController.swift +++ b/ios/MullvadVPN/ConnectViewController.swift @@ -13,7 +13,8 @@ import os class ConnectViewController: UIViewController, RootContainment, TunnelControlViewControllerDelegate, - TunnelObserver + TunnelObserver, + SelectLocationDelegate { @IBOutlet var secureLabel: UILabel! @@ -101,6 +102,31 @@ class ConnectViewController: UIViewController, } } + // MARK: - SelectLocationDelegate + + func selectLocationController(_ controller: SelectLocationController, didSelectLocation location: RelayLocation) { + controller.dismiss(animated: true) { + let relayConstraints = RelayConstraints(location: .only(location)) + + TunnelManager.shared.setRelayConstraints(relayConstraints) { [weak self] (result) in + DispatchQueue.main.async { + switch result { + case .success: + os_log(.debug, "Updated relay constraints: %{public}s", "\(relayConstraints)") + self?.connectTunnel() + + case .failure(let error): + os_log(.error, "Failed to update relay constraints: %{public}s", error.localizedDescription) + } + } + } + } + } + + func selectLocationControllerDidCancel(_ controller: SelectLocationController) { + controller.dismiss(animated: true) + } + // MARK: - Private private func updateSecureLabel() { @@ -193,21 +219,14 @@ class ConnectViewController: UIViewController, } private func showSelectLocation() { - let contentController = self.storyboard?.instantiateViewController(withIdentifier: ViewControllerIdentifier.selectLocation.rawValue) as! SelectLocationController - contentController.navigationItem.title = NSLocalizedString("Select location", comment: "") - contentController.navigationItem.largeTitleDisplayMode = .always - - let navController = UINavigationController(navigationBarClass: CustomNavigationBar.self, toolbarClass: nil) - navController.viewControllers = [contentController] - navController.navigationBar.prefersLargeTitles = true - navController.navigationBar.barStyle = .black - navController.navigationBar.tintColor = .white + let selectLocationController = SelectLocationNavigationController() + selectLocationController.selectLocationDelegate = self // Disable root controller interaction rootContainerController?.view.isUserInteractionEnabled = false - contentController.prefetchData { - self.present(navController, animated: true) + selectLocationController.prefetchData { + self.present(selectLocationController, animated: true) // Re-enable root controller interaction self.rootContainerController?.view.isUserInteractionEnabled = true @@ -220,26 +239,6 @@ class ConnectViewController: UIViewController, connectionPanel.toggleConnectionInfoVisibility() } - @IBAction func unwindFromSelectLocation(segue: UIStoryboardSegue) { - guard let selectLocationController = segue.source as? SelectLocationController else { return } - guard let selectedLocation = selectLocationController.selectedLocation else { return } - - let relayConstraints = RelayConstraints(location: .only(selectedLocation)) - - TunnelManager.shared.setRelayConstraints(relayConstraints) { [weak self] (result) in - DispatchQueue.main.async { - switch result { - case .success: - os_log(.debug, "Updated relay constraints: %{public}s", "\(relayConstraints)") - self?.connectTunnel() - - case .failure(let error): - os_log(.error, "Failed to update relay constraints: %{public}s", error.localizedDescription) - } - } - } - } - } private extension TunnelState { diff --git a/ios/MullvadVPN/SegueIdentifier.swift b/ios/MullvadVPN/SegueIdentifier.swift index d6345a4e06..3e9f2db97b 100644 --- a/ios/MullvadVPN/SegueIdentifier.swift +++ b/ios/MullvadVPN/SegueIdentifier.swift @@ -26,10 +26,6 @@ extension SegueIdentifier { case embedTunnelControls = "EmbedTunnelControls" } - enum SelectLocation: String, SegueConvertible { - case returnToConnectWithNewRelay = "ReturnToConnectWithNewRelay" - } - enum Account: String, SegueConvertible { case logout = "Logout" } diff --git a/ios/MullvadVPN/SelectLocationCell.swift b/ios/MullvadVPN/SelectLocationCell.swift index 2500be383b..40cb9e2b64 100644 --- a/ios/MullvadVPN/SelectLocationCell.swift +++ b/ios/MullvadVPN/SelectLocationCell.swift @@ -11,10 +11,10 @@ import UIKit class SelectLocationCell: BasicTableViewCell { typealias CollapseHandler = (SelectLocationCell) -> Void - @IBOutlet var locationLabel: UILabel! - @IBOutlet var statusIndicator: RelayStatusIndicatorView! - @IBOutlet var tickImageView: UIImageView! - @IBOutlet var collapseButton: UIButton! + let locationLabel = UILabel() + let statusIndicator = RelayStatusIndicatorView() + let tickImageView = UIImageView(image: UIImage(imageLiteralResourceName: "IconTick")) + let collapseButton = UIButton(type: .custom) private let chevronDown = UIImage(imageLiteralResourceName: "IconChevronDown") private let chevronUp = UIImage(imageLiteralResourceName: "IconChevronUp") @@ -48,19 +48,14 @@ class SelectLocationCell: BasicTableViewCell { } } - override func awakeFromNib() { - super.awakeFromNib() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) - indentationWidth = 16 - statusIndicator.tintColor = .white - - collapseButton.addTarget(self, action: #selector(handleCollapseButton(_ :)), for: .touchUpInside) - - updateCollapseImage() - updateDisabled() - updateBackgroundColor() + setupCell() + } - contentView.layoutMargins = preferredMargins + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { @@ -82,9 +77,57 @@ class SelectLocationCell: BasicTableViewCell { updateTickImage() } + private func setupCell() { + indentationWidth = 16 + + backgroundView = UIView() + selectedBackgroundView = UIView() + backgroundColor = .clear + contentView.layoutMargins = preferredMargins + + locationLabel.font = UIFont.systemFont(ofSize: 17) + locationLabel.textColor = .white + + statusIndicator.tintColor = .white + tickImageView.tintColor = .white + + collapseButton.tintColor = .white + collapseButton.setImage(chevronDown, for: .normal) + collapseButton.addTarget(self, action: #selector(handleCollapseButton(_ :)), for: .touchUpInside) + + [locationLabel, tickImageView, statusIndicator, collapseButton].forEach { (subview) in + subview.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(subview) + } + + updateCollapseImage() + updateDisabled() + updateBackgroundColor() + + NSLayoutConstraint.activate([ + statusIndicator.widthAnchor.constraint(equalToConstant: 16), + statusIndicator.heightAnchor.constraint(equalToConstant: 16), + statusIndicator.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor), + statusIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + + tickImageView.centerXAnchor.constraint(equalTo: statusIndicator.centerXAnchor), + tickImageView.centerYAnchor.constraint(equalTo: statusIndicator.centerYAnchor), + + locationLabel.leadingAnchor.constraint(equalTo: statusIndicator.trailingAnchor, constant: 12), + locationLabel.trailingAnchor.constraint(greaterThanOrEqualTo: collapseButton.leadingAnchor, constant: 0), + locationLabel.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor), + locationLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor), + + collapseButton.widthAnchor.constraint(equalToConstant: 64), + collapseButton.topAnchor.constraint(equalTo: contentView.topAnchor), + collapseButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + collapseButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ]) + } + private func updateTickImage() { statusIndicator.isHidden = isSelected - tickImageView?.isHidden = !isSelected + tickImageView.isHidden = !isSelected } private func updateDisabled() { diff --git a/ios/MullvadVPN/SelectLocationController.swift b/ios/MullvadVPN/SelectLocationController.swift index affb127003..77a88e2db1 100644 --- a/ios/MullvadVPN/SelectLocationController.swift +++ b/ios/MullvadVPN/SelectLocationController.swift @@ -33,13 +33,19 @@ class SelectLocationController: UITableViewController, RelayCacheObserver { private var expandedItems = [RelayLocation]() private var dataSource: DataSource? - var selectedLocation: RelayLocation? + var didSelectLocationHandler: ((RelayLocation) -> Void)? // MARK: - View lifecycle override func viewDidLoad() { super.viewDidLoad() + view.backgroundColor = .secondaryColor + + tableView.tableHeaderView = SelectLocationHeaderView(frame: CGRect(x: 0, y: 0, width: 50, height: 50)) + tableView.register(SelectLocationCell.self, forCellReuseIdentifier: kCellIdentifier) + tableView.separatorColor = .clear + dataSource = DataSource( tableView: self.tableView, cellProvider: { [weak self] (tableView, indexPath, item) -> UITableViewCell? in @@ -58,6 +64,9 @@ class SelectLocationController: UITableViewController, RelayCacheObserver { self?.collapseCell(cell) } + // Prevent overlap between cells and subcells + cell.layer.zPosition = item.zPosition + return cell }) @@ -89,14 +98,11 @@ class SelectLocationController: UITableViewController, RelayCacheObserver { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let item = dataSource?.itemIdentifier(for: indexPath) else { return } - selectedLocation = item.relayLocation - - // Return back to the main view after selecting the relay + // Disable interaction with the controller after selection tableView.isUserInteractionEnabled = false DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250)) { - self.performSegue(withIdentifier: - SegueIdentifier.SelectLocation.returnToConnectWithNewRelay.rawValue, sender: self) + self.didSelectLocationHandler?(item.relayLocation) } } @@ -371,6 +377,17 @@ private enum DataSourceItem: Hashable { } } + var zPosition: CGFloat { + switch self { + case .country: + return 3 + case .city: + return 2 + case .hostname: + return 1 + } + } + } extension ServerRelaysResponse { diff --git a/ios/MullvadVPN/SelectLocationHeaderView.swift b/ios/MullvadVPN/SelectLocationHeaderView.swift new file mode 100644 index 0000000000..d5b0268008 --- /dev/null +++ b/ios/MullvadVPN/SelectLocationHeaderView.swift @@ -0,0 +1,41 @@ +// +// SelectLocationHeaderView.swift +// MullvadVPN +// +// Created by pronebird on 22/07/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import UIKit + +class SelectLocationHeaderView: UIView { + + let textLabel = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + + layoutMargins = UIEdgeInsets(top: 24, left: 24, bottom: 24, right: 24) + + textLabel.translatesAutoresizingMaskIntoConstraints = false + textLabel.font = UIFont.systemFont(ofSize: 17) + textLabel.textColor = UIColor(white: 1, alpha: 0.6) + textLabel.numberOfLines = 0 + textLabel.text = NSLocalizedString("While connected, your real location is masked with a private and secure location in the selected region", comment: "") + + addSubview(textLabel) + + NSLayoutConstraint.activate([ + textLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), + textLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + textLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + textLabel.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), + textLabel.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/ios/MullvadVPN/SelectLocationNavigationController.swift b/ios/MullvadVPN/SelectLocationNavigationController.swift new file mode 100644 index 0000000000..2f7eb0008b --- /dev/null +++ b/ios/MullvadVPN/SelectLocationNavigationController.swift @@ -0,0 +1,61 @@ +// +// SelectLocationNavigationController.swift +// MullvadVPN +// +// Created by pronebird on 22/07/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import UIKit + +protocol SelectLocationDelegate: class { + func selectLocationController(_ controller: SelectLocationController, didSelectLocation location: RelayLocation) + func selectLocationControllerDidCancel(_ controller: SelectLocationController) +} + +class SelectLocationNavigationController: UINavigationController { + private weak var contentController: SelectLocationController? + + weak var selectLocationDelegate: SelectLocationDelegate? + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nil, bundle: nil) + } + + init() { + super.init(navigationBarClass: CustomNavigationBar.self, toolbarClass: nil) + + navigationBar.prefersLargeTitles = true + navigationBar.barStyle = .black + navigationBar.tintColor = .white + + let contentController = SelectLocationController() + contentController.navigationItem.title = NSLocalizedString("Select location", comment: "") + contentController.navigationItem.largeTitleDisplayMode = .always + contentController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDone(_:))) + + contentController.didSelectLocationHandler = { [weak self] (location) in + guard let self = self, let contentController = self.contentController else { return } + + self.selectLocationDelegate?.selectLocationController(contentController, didSelectLocation: location) + } + + self.contentController = contentController + self.viewControllers = [contentController] + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func prefetchData(_ completionHandler: @escaping () -> Void) { + contentController?.prefetchData(completionHandler: completionHandler) + } + + @objc func handleDone(_ sender: AnyObject) { + if let contentController = contentController { + selectLocationDelegate?.selectLocationControllerDidCancel(contentController) + } + } +} |
