diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-08-17 13:21:02 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-08-18 17:18:20 +0200 |
| commit | b5a441e55eacdd391a87f5550a93c8ad2492b46d (patch) | |
| tree | 60d51b54e6a5facbba4a37489e961a0f56bc90e4 /ios | |
| parent | f32cf04ca018e9572c032e9989d95a577008a7b6 (diff) | |
| download | mullvadvpn-b5a441e55eacdd391a87f5550a93c8ad2492b46d.tar.xz mullvadvpn-b5a441e55eacdd391a87f5550a93c8ad2492b46d.zip | |
Add log streamer controller
Diffstat (limited to 'ios')
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 6 | ||||
| -rw-r--r-- | ios/MullvadVPN/ApplicationConfiguration.swift | 13 | ||||
| -rw-r--r-- | ios/MullvadVPN/Base.lproj/Main.storyboard | 36 | ||||
| -rw-r--r-- | ios/MullvadVPN/LogStreamerViewController.swift | 128 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsViewController.swift | 18 |
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 } } |
