diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2019-05-29 15:24:37 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2019-05-29 15:24:37 +0200 |
| commit | 0eb13af5be6a204f9c94783bd16498101bfbb97d (patch) | |
| tree | 67ecf31751e010216a791f1278f55ee76df86747 | |
| parent | 28bb6bb3c0b4075bc45fcd9887af553fc3e0dfeb (diff) | |
| parent | 388a1dea5963efd39316c608b9f45cbd108b7345 (diff) | |
| download | mullvadvpn-0eb13af5be6a204f9c94783bd16498101bfbb97d.tar.xz mullvadvpn-0eb13af5be6a204f9c94783bd16498101bfbb97d.zip | |
Merge branch 'account-expiry-refresh'
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 8 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountExpiryRefresh.swift | 133 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountViewController.swift | 10 | ||||
| -rw-r--r-- | ios/MullvadVPN/Base.lproj/Main.storyboard | 18 | ||||
| -rw-r--r-- | ios/MullvadVPN/RootContainerViewController.swift | 3 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsViewController.swift | 34 | ||||
| -rw-r--r-- | ios/MullvadVPN/StaticTableViewDataSource.swift | 29 | ||||
| -rw-r--r-- | ios/MullvadVPN/WeakBox.swift | 17 | ||||
| -rw-r--r-- | ios/PacketTunnel/PacketTunnelProvider.swift | 8 |
9 files changed, 240 insertions, 20 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index fe1e9919b8..9d69481c7c 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 582BB1B3229574F40055B6EF /* SettingsAccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */; }; 582BB1B52295780F0055B6EF /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582BB1B42295780F0055B6EF /* AccountExpiry.swift */; }; 58461AD3228D622E00B72ECB /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58461AD2228D622E00B72ECB /* Account.swift */; }; + 58535B85229E89E7004BCBBD /* WeakBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58535B84229E89E7004BCBBD /* WeakBox.swift */; }; 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */; }; 5867A51C2248F26A005513C0 /* SegueIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867A51B2248F26A005513C0 /* SegueIdentifier.swift */; }; 587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587425C02299833500CA2045 /* RootContainerViewController.swift */; }; @@ -48,6 +49,7 @@ 58F19E31228B2AEB00C7710B /* JSONRequestProcedure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F19E30228B2AEB00C7710B /* JSONRequestProcedure.swift */; }; 58F19E33228B383300C7710B /* AccountVerificationProcedure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F19E32228B383300C7710B /* AccountVerificationProcedure.swift */; }; 58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */; }; + 58F83F08229D3F560086FCE3 /* AccountExpiryRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F83F07229D3F560086FCE3 /* AccountExpiryRefresh.swift */; }; 58FFE444228C82A00036F391 /* UserDefaultsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FFE443228C82A00036F391 /* UserDefaultsInteractor.swift */; }; /* End PBXBuildFile section */ @@ -86,6 +88,7 @@ 582BB1B2229574F40055B6EF /* SettingsAccountCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountCell.swift; sourceTree = "<group>"; }; 582BB1B42295780F0055B6EF /* AccountExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiry.swift; sourceTree = "<group>"; }; 58461AD2228D622E00B72ECB /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; }; + 58535B84229E89E7004BCBBD /* WeakBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakBox.swift; sourceTree = "<group>"; }; 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslucentButtonBlurView.swift; sourceTree = "<group>"; }; 5866F39B2243B82D00168AE5 /* MullvadVPN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MullvadVPN.entitlements; sourceTree = "<group>"; }; 5867A51B2248F26A005513C0 /* SegueIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegueIdentifier.swift; sourceTree = "<group>"; }; @@ -122,6 +125,7 @@ 58F19E30228B2AEB00C7710B /* JSONRequestProcedure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONRequestProcedure.swift; sourceTree = "<group>"; }; 58F19E32228B383300C7710B /* AccountVerificationProcedure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountVerificationProcedure.swift; sourceTree = "<group>"; }; 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerActivityIndicatorView.swift; sourceTree = "<group>"; }; + 58F83F07229D3F560086FCE3 /* AccountExpiryRefresh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryRefresh.swift; sourceTree = "<group>"; }; 58FFE443228C82A00036F391 /* UserDefaultsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsInteractor.swift; sourceTree = "<group>"; }; 627D4CE562B85202FCFA0EB1 /* Pods-MullvadVPN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MullvadVPN.debug.xcconfig"; path = "Target Support Files/Pods-MullvadVPN/Pods-MullvadVPN.debug.xcconfig"; sourceTree = "<group>"; }; 9F1362F46063B1D06EB0C685 /* Pods-PacketTunnel.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PacketTunnel.debug.xcconfig"; path = "Target Support Files/Pods-PacketTunnel/Pods-PacketTunnel.debug.xcconfig"; sourceTree = "<group>"; }; @@ -183,6 +187,7 @@ children = ( 58461AD2228D622E00B72ECB /* Account.swift */, 582BB1B42295780F0055B6EF /* AccountExpiry.swift */, + 58F83F07229D3F560086FCE3 /* AccountExpiryRefresh.swift */, 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */, 58CCA01D2242787B004F3011 /* AccountTextField.swift */, 58F19E32228B383300C7710B /* AccountVerificationProcedure.swift */, @@ -220,6 +225,7 @@ 58CCA0152242560B004F3011 /* UIColor+Palette.swift */, 58FFE443228C82A00036F391 /* UserDefaultsInteractor.swift */, 581CBCE52296B97300727D7F /* ViewControllerIdentifier.swift */, + 58535B84229E89E7004BCBBD /* WeakBox.swift */, 587B08E1229460C1000E6F17 /* WebLinks.swift */, ); path = MullvadVPN; @@ -436,6 +442,7 @@ 58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */, 58ADDB40227B1E7100FAFEA7 /* Optional+Unwrap.swift in Sources */, 58CCA0162242560B004F3011 /* UIColor+Palette.swift in Sources */, + 58F83F08229D3F560086FCE3 /* AccountExpiryRefresh.swift in Sources */, 587CBFE322807F530028DED3 /* UIColor+Helpers.swift in Sources */, 581CBCEC2298041B00727D7F /* SettingsAppVersionCell.swift in Sources */, 587B08E2229460C1000E6F17 /* WebLinks.swift in Sources */, @@ -443,6 +450,7 @@ 58ADDB3E227B1CD900FAFEA7 /* MullvadAPI.swift in Sources */, 587B08E0229433EB000E6F17 /* LoginState.swift in Sources */, 582BB1AF229566420055B6EF /* SettingsCell.swift in Sources */, + 58535B85229E89E7004BCBBD /* WeakBox.swift in Sources */, 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */, 5888AD83227B11080051EB06 /* SelectLocationCell.swift in Sources */, 58CE5E66224146200008646E /* LoginViewController.swift in Sources */, diff --git a/ios/MullvadVPN/AccountExpiryRefresh.swift b/ios/MullvadVPN/AccountExpiryRefresh.swift new file mode 100644 index 0000000000..14feadccd1 --- /dev/null +++ b/ios/MullvadVPN/AccountExpiryRefresh.swift @@ -0,0 +1,133 @@ +// +// AccountExpiryRefresh.swift +// MullvadVPN +// +// Created by pronebird on 28/05/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import Foundation +import os.log +import ProcedureKit + +private let kRefreshIntervalSeconds: TimeInterval = 60 + +/// A class that manages the periodic account expiry updates. +/// All public methods are thread safe. +class AccountExpiryRefresh { + + /// A singleton instance of the AccountExpiryRefresh + static let shared = AccountExpiryRefresh() + + private let procedureQueue: ProcedureQueue = { + let queue = ProcedureQueue() + queue.qualityOfService = .utility + return queue + }() + + /// Recursive lock used to manipulate observers + private let lock = NSRecursiveLock() + private var observers = [WeakBox<Observer>]() + + private init() {} + + /// Adds the observer for periodic account expiry updates. + func startMonitoringUpdates(with block: @escaping (Date) -> Void) -> Observer { + let observer = Observer(with: block) + + addObserver(observer) + + return observer + } + + /// Register observer and start updating the account expiry if hasn't started yet + private func addObserver(_ observer: Observer) { + lock.withCriticalScope { + if observers.isEmpty { + procedureQueue.addOperation(makePeriodicUpdateProcedure()) + } + + observers.append(WeakBox(observer)) + } + + } + + /// Remove all boxed values whos underlying weak value has been released + private func compactObservers() { + lock.withCriticalScope { + observers.removeAll { $0.unboxed == nil } + } + } + + /// Broadcast the new expiry to the observers + private func notifyObservers(with newExpiry: Date) { + let strongObservers = lock.withCriticalScope { observers.compactMap { $0.unboxed } } + + DispatchQueue.main.async { + strongObservers.forEach { $0.notify(with: newExpiry) } + } + } + + /// Returns true if the repeat procedure should keep running + private func shouldKeepRefreshing() -> Bool { + return lock.withCriticalScope { + compactObservers() + + return !observers.isEmpty + } + } + + /// Create a procedure that will repeat itself with a constant interval until there are no + /// observers left. + private func makePeriodicUpdateProcedure() -> RepeatProcedure<Operation> { + let repeatProcedure = RepeatProcedure(wait: .constant(kRefreshIntervalSeconds)) { [weak self] () -> Operation? in + // Stop repeating the procedure if no-one is listening + guard let self = self, self.shouldKeepRefreshing() else { return nil } + + // Create the procedure to feed the account token saved in preferences into the + // request procedure + let getAccountTokenProcedure = ResultProcedure(block: { Account.token }) + + // Create the API request procedure + let requestProcedure = MullvadAPI.getAccountExpiry() + .injectResult(from: getAccountTokenProcedure) + + // Create the procedure to save the received account expiry and notify the observers + let saveAccountExpiryProcedure = TransformProcedure { [weak self] (response) throws -> Void in + let userDefaultsInteractor = UserDefaultsInteractor.sharedApplicationGroupInteractor + + let newAccountExpiry = try response.result.get() + let oldAccountExpiry = userDefaultsInteractor.accountExpiry + + if oldAccountExpiry != newAccountExpiry { + userDefaultsInteractor.accountExpiry = newAccountExpiry + + self?.notifyObservers(with: newAccountExpiry) + } + }.injectResult(from: requestProcedure) + + // Return the group + return GroupProcedure(operations: [getAccountTokenProcedure, requestProcedure, saveAccountExpiryProcedure]) + } + + // Make sure that only one such operation runs at a time + repeatProcedure.addCondition(MutuallyExclusive<AccountExpiryRefresh>()) + + return repeatProcedure + } + + /// The account expiry observer. + class Observer { + typealias Block = (Date) -> Void + private let block: Block + + fileprivate init(with block: @escaping Block) { + self.block = block + } + + fileprivate func notify(with expiryDate: Date) { + block(expiryDate) + } + } + +} diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift index b9f5f02e7f..a52cee7e83 100644 --- a/ios/MullvadVPN/AccountViewController.swift +++ b/ios/MullvadVPN/AccountViewController.swift @@ -13,10 +13,13 @@ class AccountViewController: UIViewController { @IBOutlet var accountLabel: UILabel! @IBOutlet var expiryLabel: UILabel! + private var accountExpiryObserver: AccountExpiryRefresh.Observer? + override func viewDidLoad() { super.viewDidLoad() updateView() + startAccountExpiryUpdates() } // MARK: - Actions @@ -48,4 +51,11 @@ class AccountViewController: UIViewController { } } } + + private func startAccountExpiryUpdates() { + accountExpiryObserver = AccountExpiryRefresh.shared + .startMonitoringUpdates(with: { [weak self] (expiryDate) in + self?.updateView() + }) + } } diff --git a/ios/MullvadVPN/Base.lproj/Main.storyboard b/ios/MullvadVPN/Base.lproj/Main.storyboard index 7162a8a2c5..6a9a7713d3 100644 --- a/ios/MullvadVPN/Base.lproj/Main.storyboard +++ b/ios/MullvadVPN/Base.lproj/Main.storyboard @@ -26,17 +26,17 @@ <rect key="frame" x="0.0" y="0.0" width="375" height="93"/> <subviews> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LogoIcon" translatesAutoresizingMaskIntoConstraints="NO" id="cKg-hE-JsS"> - <rect key="frame" x="12" y="31.5" width="49.000000000000014" height="50"/> + <rect key="frame" x="12" y="48.5" width="16" height="16"/> </imageView> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uXv-Tf-PET"> - <rect key="frame" x="327" y="44.5" width="24" height="24"/> + <rect key="frame" x="343" y="45.5" width="16" height="22"/> <state key="normal" image="IconSettings"/> <connections> <segue destination="Kqv-qu-mfF" kind="presentation" identifier="ShowSettings" id="cxu-NC-VeP"/> </connections> </button> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="MULLVAD VPN" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dqy-A0-TdV"> - <rect key="frame" x="69" y="42" width="168" height="29"/> + <rect key="frame" x="36" y="42" width="168" height="29"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="24"/> <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> @@ -49,11 +49,11 @@ <constraint firstAttribute="bottomMargin" secondItem="dqy-A0-TdV" secondAttribute="bottom" constant="22" id="YTk-xg-wIk"/> <constraint firstItem="uXv-Tf-PET" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="dqy-A0-TdV" secondAttribute="trailing" constant="8" symbolic="YES" id="ZEb-xZ-1ga"/> <constraint firstItem="uXv-Tf-PET" firstAttribute="centerY" secondItem="dqy-A0-TdV" secondAttribute="centerY" id="gCl-OS-ONw"/> - <constraint firstItem="cKg-hE-JsS" firstAttribute="leading" secondItem="cw4-px-5hC" secondAttribute="leadingMargin" constant="-12" id="hGJ-yd-hnp"/> + <constraint firstItem="cKg-hE-JsS" firstAttribute="leading" secondItem="cw4-px-5hC" secondAttribute="leadingMargin" id="hGJ-yd-hnp"/> <constraint firstItem="dqy-A0-TdV" firstAttribute="top" secondItem="cw4-px-5hC" secondAttribute="topMargin" constant="22" id="mMF-ha-mRO"/> <constraint firstItem="dqy-A0-TdV" firstAttribute="leading" secondItem="cKg-hE-JsS" secondAttribute="trailing" constant="8" id="q8s-25-ASt"/> </constraints> - <edgeInsets key="layoutMargins" top="20" left="24" bottom="0.0" right="24"/> + <edgeInsets key="layoutMargins" top="20" left="12" bottom="0.0" right="16"/> </view> </subviews> <color key="backgroundColor" red="0.16078431369999999" green="0.30196078430000001" blue="0.45098039220000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> @@ -258,7 +258,7 @@ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="MapBackground" translatesAutoresizingMaskIntoConstraints="NO" id="3Ck-JT-ogd"> - <rect key="frame" x="0.0" y="0.0" width="375" height="667.00000000000023"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> </imageView> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="shY-Lj-oYx"> <rect key="frame" x="24" y="543" width="327" height="100"/> @@ -425,7 +425,11 @@ </connections> </tableViewController> <placeholder placeholderIdentifier="IBFirstResponder" id="sR5-ix-4x7" userLabel="First Responder" sceneMemberID="firstResponder"/> - <customObject id="9xf-6a-8vR" customClass="SettingsTableViewDataSource" customModule="MullvadVPN" customModuleProvider="target"/> + <customObject id="9xf-6a-8vR" customClass="SettingsTableViewDataSource" customModule="MullvadVPN" customModuleProvider="target"> + <connections> + <outlet property="tableView" destination="6Gz-UM-orK" id="Ipk-3P-ycO"/> + </connections> + </customObject> </objects> <point key="canvasLocation" x="1690" y="-832"/> </scene> diff --git a/ios/MullvadVPN/RootContainerViewController.swift b/ios/MullvadVPN/RootContainerViewController.swift index 18fbb91302..7d1990e507 100644 --- a/ios/MullvadVPN/RootContainerViewController.swift +++ b/ios/MullvadVPN/RootContainerViewController.swift @@ -273,9 +273,6 @@ class RootContainerViewController: UIViewController { var layoutMargins = headerBarView.layoutMargins layoutMargins.top = offsetTop - layoutMargins.left = view.layoutMargins.left - layoutMargins.right = view.layoutMargins.right - layoutMargins.bottom = 0 if layoutMargins != headerBarView.layoutMargins { headerBarView.layoutMargins = layoutMargins diff --git a/ios/MullvadVPN/SettingsViewController.swift b/ios/MullvadVPN/SettingsViewController.swift index 88b1ef6555..7a4ca8dc37 100644 --- a/ios/MullvadVPN/SettingsViewController.swift +++ b/ios/MullvadVPN/SettingsViewController.swift @@ -18,9 +18,34 @@ class SettingsViewController: UITableViewController { case appVersion = "AppVersion" } + private var accountExpiryObserver: AccountExpiryRefresh.Observer? + private weak var accountRow: StaticTableViewRow? + override func viewDidLoad() { super.viewDidLoad() + setupDataSource() + startAccountExpiryUpdates() + } + + // MARK: - IBActions + + @IBAction func handleDismiss() { + dismiss(animated: true) + } + + // MARK: - Private + + private func startAccountExpiryUpdates() { + accountExpiryObserver = AccountExpiryRefresh.shared + .startMonitoringUpdates(with: { [weak self] (expiryDate) in + guard let self = self, let accountRow = self.accountRow else { return } + + self.staticDataSource.reloadRows([accountRow], with: .automatic) + }) + } + + private func setupDataSource() { if Account.isLoggedIn { let topSection = StaticTableViewSection() let accountRow = StaticTableViewRow(reuseIdentifier: CellIdentifier.account.rawValue) { (_, cell) in @@ -28,6 +53,9 @@ class SettingsViewController: UITableViewController { cell.accountExpiryDate = Account.expiry } + + self.accountRow = accountRow + topSection.addRows([accountRow]) staticDataSource.addSections([topSection]) } @@ -45,12 +73,6 @@ class SettingsViewController: UITableViewController { staticDataSource.addSections([middleSection]) } - // MARK: - IBActions - - @IBAction func handleDismiss() { - dismiss(animated: true) - } - } class SettingsTableViewDataSource: StaticTableViewDataSource { diff --git a/ios/MullvadVPN/StaticTableViewDataSource.swift b/ios/MullvadVPN/StaticTableViewDataSource.swift index e9504ee567..11145bf607 100644 --- a/ios/MullvadVPN/StaticTableViewDataSource.swift +++ b/ios/MullvadVPN/StaticTableViewDataSource.swift @@ -39,12 +39,41 @@ class StaticTableViewSection { class StaticTableViewDataSource: NSObject, UITableViewDataSource, UITableViewDelegate { + @IBOutlet weak var tableView: UITableView? + private(set) var sections = [StaticTableViewSection]() func addSections(_ sections: [StaticTableViewSection]) { self.sections.append(contentsOf: sections) } + func reloadRows(_ rows: [StaticTableViewRow], with animation: UITableView.RowAnimation) { + let indexPaths = rows.compactMap { indexPathForRow($0) } + + tableView?.reloadRows(at: indexPaths, with: animation) + } + + func indexPathForRow(_ searchRow: StaticTableViewRow) -> IndexPath? { + var sectionIndex = 0 + + for section in sections { + let visibleRows = section.rows.filter { !$0.isHidden } + + // skip incrementing the section index since invisible sections are normally collapsed + guard visibleRows.count > 0 else { + continue + } + + if let rowIndex = visibleRows.firstIndex(where: { $0 === searchRow }) { + return IndexPath(row: rowIndex, section: sectionIndex) + } + + sectionIndex += 1 + } + + return nil + } + // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { diff --git a/ios/MullvadVPN/WeakBox.swift b/ios/MullvadVPN/WeakBox.swift new file mode 100644 index 0000000000..d8a2a86e8b --- /dev/null +++ b/ios/MullvadVPN/WeakBox.swift @@ -0,0 +1,17 @@ +// +// WeakBox.swift +// MullvadVPN +// +// Created by pronebird on 29/05/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import Foundation + +final class WeakBox<T: AnyObject> { + weak var unboxed: T? + + init(_ value: T) { + unboxed = value + } +} diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift index 914bce9c2c..4b4fedf307 100644 --- a/ios/PacketTunnel/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider.swift @@ -13,24 +13,24 @@ class PacketTunnelProvider: NEPacketTunnelProvider { override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { // Add code here to start the process of connecting the tunnel. } - + override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { // Add code here to start the process of stopping the tunnel. completionHandler() } - + override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { // Add code here to handle the message. if let handler = completionHandler { handler(messageData) } } - + override func sleep(completionHandler: @escaping () -> Void) { // Add code here to get ready to sleep. completionHandler() } - + override func wake() { // Add code here to wake up. } |
