summaryrefslogtreecommitdiffhomepage
path: root/ios
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2020-08-17 13:21:02 +0200
committerAndrej Mihajlov <and@mullvad.net>2020-08-18 17:18:20 +0200
commitb5a441e55eacdd391a87f5550a93c8ad2492b46d (patch)
tree60d51b54e6a5facbba4a37489e961a0f56bc90e4 /ios
parentf32cf04ca018e9572c032e9989d95a577008a7b6 (diff)
downloadmullvadvpn-b5a441e55eacdd391a87f5550a93c8ad2492b46d.tar.xz
mullvadvpn-b5a441e55eacdd391a87f5550a93c8ad2492b46d.zip
Add log streamer controller
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj6
-rw-r--r--ios/MullvadVPN/ApplicationConfiguration.swift13
-rw-r--r--ios/MullvadVPN/Base.lproj/Main.storyboard36
-rw-r--r--ios/MullvadVPN/LogStreamerViewController.swift128
-rw-r--r--ios/MullvadVPN/SettingsViewController.swift18
5 files changed, 195 insertions, 6 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 1052ad7602..43c8b30ed0 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -154,6 +154,7 @@
58BFA5CC22A7CE1F00A6173D /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; };
58BFA5CD22A7CE1F00A6173D /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; };
58C3A4B222456F1B00340BDB /* AccountInputGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */; };
+ 58C3B06724EA768100C0348E /* LogStreamerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3B06624EA768100C0348E /* LogStreamerViewController.swift */; };
58C6B34F22BB7AC0003C19AD /* IPAddressRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C6B34E22BB7AC0003C19AD /* IPAddressRange.swift */; };
58C6B35122BB7CFD003C19AD /* IPAddressRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C6B34E22BB7AC0003C19AD /* IPAddressRange.swift */; };
58C6B35422BB87C4003C19AD /* WireguardPrivateKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C6B35322BB87C4003C19AD /* WireguardPrivateKey.swift */; };
@@ -339,6 +340,7 @@
58BFA5C522A7C97F00A6173D /* RelayCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCache.swift; sourceTree = "<group>"; };
58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationConfiguration.swift; sourceTree = "<group>"; };
58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountInputGroupView.swift; sourceTree = "<group>"; };
+ 58C3B06624EA768100C0348E /* LogStreamerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogStreamerViewController.swift; sourceTree = "<group>"; };
58C6B34E22BB7AC0003C19AD /* IPAddressRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPAddressRange.swift; sourceTree = "<group>"; };
58C6B35322BB87C4003C19AD /* WireguardPrivateKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireguardPrivateKey.swift; sourceTree = "<group>"; };
58C6B35D22BBBFE3003C19AD /* Data+HexCoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+HexCoding.swift"; sourceTree = "<group>"; };
@@ -516,7 +518,6 @@
58CE5E62224146200008646E /* MullvadVPN */ = {
isa = PBXGroup;
children = (
- 5815039F24D6ECF200C9C50E /* Logging */,
587AD7C92342283900E93A53 /* Account.swift */,
582BB1B42295780F0055B6EF /* AccountExpiry.swift */,
58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */,
@@ -560,7 +561,9 @@
58CE5E6C224146210008646E /* LaunchScreen.storyboard */,
58A1AA8623F43901009F7EA6 /* Location.swift */,
58BA692D23E99EFF009DC256 /* Locking.swift */,
+ 5815039F24D6ECF200C9C50E /* Logging */,
58CE5E65224146200008646E /* LoginViewController.swift */,
+ 58C3B06624EA768100C0348E /* LogStreamerViewController.swift */,
58CE5E67224146200008646E /* Main.storyboard */,
5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */,
58CB0EDF24B86751001EF0D8 /* MullvadRest.swift */,
@@ -1036,6 +1039,7 @@
587AD7C623421D7000E93A53 /* TunnelSettings.swift in Sources */,
581503A324D6F1EC00C9C50E /* ChainedError+Logger.swift in Sources */,
58FD5BF024238EB300112C88 /* SKProduct+Formatting.swift in Sources */,
+ 58C3B06724EA768100C0348E /* LogStreamerViewController.swift in Sources */,
58561C99239A5D1500BD6B5E /* IPEndpoint.swift in Sources */,
58FD5BF22424F7D700112C88 /* UserInterfaceInteractionRestriction.swift in Sources */,
5811DE50239014550011EB53 /* NEVPNStatus+Debug.swift in Sources */,
diff --git a/ios/MullvadVPN/ApplicationConfiguration.swift b/ios/MullvadVPN/ApplicationConfiguration.swift
index 90e4849882..8dc5063fcb 100644
--- a/ios/MullvadVPN/ApplicationConfiguration.swift
+++ b/ios/MullvadVPN/ApplicationConfiguration.swift
@@ -15,4 +15,17 @@ class ApplicationConfiguration {
/// The application identifier for the PacketTunnel extension
static let packetTunnelExtensionIdentifier = "net.mullvad.MullvadVPN.PacketTunnel"
+
+ /// The application log files
+ static var logFileURLs: [URL] {
+ let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Self.securityGroupIdentifier)
+ let fileNames = ["net.mullvad.MullvadVPN", "net.mullvad.MullvadVPN.PacketTunnel"]
+
+ return fileNames.compactMap { (fileName) -> URL? in
+ return containerURL?
+ .appendingPathComponent("Logs", isDirectory: true)
+ .appendingPathComponent(fileName, isDirectory: false)
+ .appendingPathExtension("log")
+ }
+ }
}
diff --git a/ios/MullvadVPN/Base.lproj/Main.storyboard b/ios/MullvadVPN/Base.lproj/Main.storyboard
index 7bb8bab957..f2ce083c98 100644
--- a/ios/MullvadVPN/Base.lproj/Main.storyboard
+++ b/ios/MullvadVPN/Base.lproj/Main.storyboard
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ZwP-1v-DUg">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ZwP-1v-DUg">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
@@ -406,6 +406,32 @@
<outlet property="titleLabel" destination="Amw-A3-ePS" id="cGS-cX-LXr"/>
</connections>
</tableViewCell>
+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Basic" id="kzz-4X-xg1" customClass="SettingsBasicCell" customModule="MullvadVPN" customModuleProvider="target">
+ <rect key="frame" x="0.0" y="186" width="375" height="43.5"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="kzz-4X-xg1" id="KpJ-UC-PyV">
+ <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="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PWF-Y6-mDf">
+ <rect key="frame" x="16" y="11" width="343" 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="PWF-Y6-mDf" firstAttribute="top" secondItem="KpJ-UC-PyV" secondAttribute="topMargin" id="7c8-Np-uyI"/>
+ <constraint firstAttribute="trailingMargin" secondItem="PWF-Y6-mDf" secondAttribute="trailing" id="G6s-Z9-acp"/>
+ <constraint firstAttribute="bottomMargin" secondItem="PWF-Y6-mDf" secondAttribute="bottom" id="ICb-f1-Zux"/>
+ <constraint firstItem="PWF-Y6-mDf" firstAttribute="leading" secondItem="KpJ-UC-PyV" secondAttribute="leadingMargin" id="teH-t2-aJn"/>
+ </constraints>
+ </tableViewCellContentView>
+ <color key="backgroundColor" name="Primary"/>
+ <connections>
+ <outlet property="titleLabel" destination="PWF-Y6-mDf" id="G5i-Wm-WZN"/>
+ </connections>
+ </tableViewCell>
</prototypes>
<sections/>
<connections>
@@ -905,7 +931,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JYh-33-d0O">
- <rect key="frame" x="0.0" y="0.0" width="375" height="598"/>
+ <rect key="frame" x="0.0" y="0.0" width="375" height="597"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="N9k-cQ-tlw" userLabel="Content view">
<rect key="frame" x="0.0" y="0.0" width="375" height="558"/>
@@ -944,7 +970,7 @@
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Cas-Tk-gcz" customClass="LinkButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="20" y="516" width="20" height="22"/>
+ <rect key="frame" x="20" y="516" width="128" height="22"/>
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="18"/>
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<state key="normal" title="Privacy Policy" image="IconExtlink"/>
@@ -980,10 +1006,10 @@
</constraints>
</scrollView>
<view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="16P-Q0-ZO9" userLabel="Footer">
- <rect key="frame" x="0.0" y="598" width="375" height="69"/>
+ <rect key="frame" x="0.0" y="597" width="375" height="70"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ttw-7B-1MM" customClass="AppButton" customModule="MullvadVPN" customModuleProvider="target">
- <rect key="frame" x="16" y="24" width="343" height="21"/>
+ <rect key="frame" x="16" y="24" width="343" height="22"/>
<accessibility key="accessibilityConfiguration" identifier="AgreeButton"/>
<state key="normal" title="Agree and continue" backgroundImage="DefaultButton">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
diff --git a/ios/MullvadVPN/LogStreamerViewController.swift b/ios/MullvadVPN/LogStreamerViewController.swift
new file mode 100644
index 0000000000..4052da4dba
--- /dev/null
+++ b/ios/MullvadVPN/LogStreamerViewController.swift
@@ -0,0 +1,128 @@
+//
+// LogStreamerViewController.swift
+// MullvadVPN
+//
+// Created by pronebird on 17/08/2020.
+// Copyright © 2020 Mullvad VPN AB. All rights reserved.
+//
+
+#if DEBUG
+
+import Foundation
+import UIKit
+
+class LogStreamerViewController: UIViewController, UITextViewDelegate {
+
+ private let textView = UITextView()
+ private let streamer: LogStreamer<UTF8>
+
+ private var autoScrollButtonItem: UIBarButtonItem {
+ return UIBarButtonItem(barButtonSystemItem: autoScroll ? .pause : .play, target: self, action: #selector(handleToggleAutoscroll(_:)))
+ }
+
+ private var dismissButtonItem: UIBarButtonItem {
+ return UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDismissButton(_:)))
+ }
+
+ var autoScroll: Bool = true {
+ didSet {
+ updateAutoScrollBarItem()
+ handleAutoScroll()
+ }
+ }
+
+ init(fileURLs: [URL]) {
+ streamer = LogStreamer(fileURLs: fileURLs)
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ // MARK: - View lifecycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ navigationItem.title = NSLocalizedString("App logs", comment: "")
+ navigationItem.leftBarButtonItem = autoScrollButtonItem
+ navigationItem.rightBarButtonItem = dismissButtonItem
+
+ addSubviews()
+ startStreamer()
+ }
+
+ // MARK: - UITextViewDelegate
+
+ func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
+ let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)
+
+ // Disable autoscroll if user scrolled up
+ if translation.y > 0 {
+ autoScroll = false
+ }
+ }
+
+ func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
+ // Disable autoscroll when user requested scroll to top
+ autoScroll = false
+ return true
+ }
+
+ // MARK: - Private
+
+ private func addSubviews() {
+ textView.translatesAutoresizingMaskIntoConstraints = false
+ textView.isEditable = false
+ textView.delegate = self
+
+ view.addSubview(textView)
+
+ NSLayoutConstraint.activate([
+ textView.topAnchor.constraint(equalTo: view.topAnchor),
+ textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ textView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+ ])
+ }
+
+ private func startStreamer() {
+ self.streamer.start { [weak self] (str) in
+ guard let self = self else { return }
+
+ DispatchQueue.main.async {
+ self.textView.insertText("\(str)\n")
+ self.handleAutoScroll()
+ }
+ }
+ }
+
+ private func handleAutoScroll() {
+ if autoScroll && !textView.isTracking && !textView.isDragging {
+ scrollToBottom()
+ }
+ }
+
+ private func scrollToBottom() {
+ let textRange = NSRange(..<textView.text.endIndex, in: textView.text)
+
+ textView.scrollRangeToVisible(textRange)
+ }
+
+ private func updateAutoScrollBarItem() {
+ navigationItem.leftBarButtonItem = autoScrollButtonItem
+ }
+
+ // MARK: - Actions
+
+ @objc func handleDismissButton(_ sender: Any) {
+ dismiss(animated: true)
+ }
+
+ @objc func handleToggleAutoscroll(_ sender: Any) {
+ autoScroll = !autoScroll
+ }
+}
+
+#endif
diff --git a/ios/MullvadVPN/SettingsViewController.swift b/ios/MullvadVPN/SettingsViewController.swift
index bee3d3a458..6991202afc 100644
--- a/ios/MullvadVPN/SettingsViewController.swift
+++ b/ios/MullvadVPN/SettingsViewController.swift
@@ -21,6 +21,7 @@ class SettingsViewController: UITableViewController {
case account = "Account"
case appVersion = "AppVersion"
case basicDisclosure = "BasicDisclosure"
+ case basic = "Basic"
}
private weak var accountRow: StaticTableViewRow?
@@ -99,6 +100,23 @@ class SettingsViewController: UITableViewController {
middleSection.addRows([versionRow])
staticDataSource.addSections([middleSection])
+
+ #if DEBUG
+ let logStreamerRow = StaticTableViewRow(reuseIdentifier: CellIdentifier.basic.rawValue) { (_, cell) in
+ let cell = cell as! SettingsBasicCell
+
+ cell.titleLabel.text = NSLocalizedString("App logs", comment: "")
+ }
+ logStreamerRow.actionBlock = { [weak self] (indexPath) in
+ let logController = LogStreamerViewController(fileURLs: ApplicationConfiguration.logFileURLs)
+ let navController = UINavigationController(rootViewController: logController)
+
+ navController.modalPresentationStyle = .fullScreen
+
+ self?.present(navController, animated: true)
+ }
+ middleSection.addRows([logStreamerRow])
+ #endif
}
}