summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj8
-rw-r--r--ios/MullvadVPN/AccountExpiryRefresh.swift133
-rw-r--r--ios/MullvadVPN/AccountViewController.swift10
-rw-r--r--ios/MullvadVPN/Base.lproj/Main.storyboard18
-rw-r--r--ios/MullvadVPN/RootContainerViewController.swift3
-rw-r--r--ios/MullvadVPN/SettingsViewController.swift34
-rw-r--r--ios/MullvadVPN/StaticTableViewDataSource.swift29
-rw-r--r--ios/MullvadVPN/WeakBox.swift17
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider.swift8
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.
}