Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b88874b38b | ||
|
|
f55f3ea32d | ||
|
|
c843bd76a2 | ||
|
|
4dd2339ed8 | ||
|
|
280526bef4 | ||
|
|
34caffd4a7 | ||
|
|
9e19b457e2 | ||
|
|
e6846953b7 | ||
|
|
6d78aeac7b | ||
|
|
5d94fe3a0d | ||
|
|
fb680d669b | ||
|
|
6409e5eaf3 | ||
|
|
39ca9dbdb1 | ||
|
|
27ab2a621a |
@@ -1397,7 +1397,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
CURRENT_PROJECT_VERSION = 29;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1416,7 +1416,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
CURRENT_PROJECT_VERSION = 29;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1435,7 +1435,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
CURRENT_PROJECT_VERSION = 29;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
@@ -1453,7 +1453,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 27;
|
||||
CURRENT_PROJECT_VERSION = 29;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
|
||||
@@ -53,3 +53,5 @@ That means, AppCheck does not have to be active in the foreground all the time.
|
||||
|
||||
*information will be added soon™*
|
||||
|
||||
For now, go to the results page at [https://appchk.de/](https://appchk.de/).
|
||||
Btw. we are searching for [help](https://appchk.de/help/) on our ongoing research project.
|
||||
|
||||
@@ -30,14 +30,16 @@ struct NotificationBanner {
|
||||
img.tintColor = fg
|
||||
view.addSubview(lbl)
|
||||
view.addSubview(img)
|
||||
img.anchor([.centerY], to: view.layoutMarginsGuide)
|
||||
lbl.anchor([.top, .bottom, .trailing], to: view.layoutMarginsGuide)
|
||||
img.anchor([.centerY], to: lbl)
|
||||
lbl.anchor([.bottom, .trailing], to: view.layoutMarginsGuide)
|
||||
img.widthAnchor =&= 25
|
||||
img.heightAnchor =&= 25
|
||||
if #available(iOS 11, *) {
|
||||
img.leadingAnchor =&= view.layoutMarginsGuide.leadingAnchor
|
||||
lbl.topAnchor =&= view.layoutMarginsGuide.topAnchor
|
||||
} else {
|
||||
img.leadingAnchor =&= view.leadingAnchor + 8
|
||||
lbl.topAnchor =&= view.topAnchor + 8
|
||||
}
|
||||
lbl.leadingAnchor =&= img.trailingAnchor + 8
|
||||
img.bottomAnchor =<= view.bottomAnchor - 8 | .init(rawValue: 999)
|
||||
|
||||
@@ -405,7 +405,7 @@ extension SQLiteDatabase {
|
||||
}
|
||||
|
||||
func appBundleList() -> [AppBundleInfo]? {
|
||||
try? run(sql: "SELECT appid, title, subtitle FROM rec WHERE appid IS NOT NULL GROUP BY appid ORDER BY title ASC;") {
|
||||
try? run(sql: "SELECT appid, title, subtitle FROM rec WHERE appid IS NOT NULL GROUP BY appid ORDER BY lower(title) ASC;") {
|
||||
allRows($0) {
|
||||
AppBundleInfo(col_text($0, 0)!, col_text($0, 1), col_text($0, 2))
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ extension URL {
|
||||
return components.url
|
||||
}
|
||||
|
||||
func download(to file: URL, onSuccess: @escaping () -> Void) {
|
||||
URLSession.shared.downloadTask(with: self) { location, response, error in
|
||||
@discardableResult func download(to file: URL, onSuccess: @escaping () -> Void) -> URLSessionDownloadTask {
|
||||
let task = URLSession.shared.downloadTask(with: self) { location, response, error in
|
||||
if let loc = location {
|
||||
try? FileManager.default.removeItem(at: file)
|
||||
do {
|
||||
@@ -51,6 +51,8 @@ extension URL {
|
||||
NSLog("[VPN.ERROR] \(error)")
|
||||
}
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
task.resume()
|
||||
return task
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="hm5-7q-Zfi">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.3" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="hm5-7q-Zfi">
|
||||
<device id="retina4_0" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
@@ -128,6 +128,7 @@
|
||||
<outlet property="buttonView" destination="La3-9e-6TK" id="UMg-xx-6OV"/>
|
||||
<outlet property="headerView" destination="ppJ-js-Wwz" id="68u-8M-R2Q"/>
|
||||
<outlet property="runningView" destination="9Yj-FX-eFd" id="L2C-YR-2HN"/>
|
||||
<outlet property="startSegment" destination="2MI-6l-YQt" id="Jun-ct-Xag"/>
|
||||
<outlet property="stopButton" destination="vAq-EZ-Gmx" id="XiW-1H-I9y"/>
|
||||
<outlet property="timeLabel" destination="rbR-np-cXD" id="EEe-8F-HT6"/>
|
||||
</connections>
|
||||
@@ -306,9 +307,6 @@
|
||||
</rightBarButtonItems>
|
||||
</navigationItem>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
<connections>
|
||||
<outlet property="shareButton" destination="UkE-Wi-JjW" id="9f1-WG-k0N"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="lan-I9-b0a" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
@@ -497,18 +495,18 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<navigationBar contentMode="scaleToFill" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2yS-xK-Wac">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<gestureRecognizers/>
|
||||
<items>
|
||||
<navigationItem title="Edit" id="JSi-oz-VRx">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="TGg-60-wZW">
|
||||
<connections>
|
||||
<action selector="didTapCancel:" destination="VRk-wv-rhk" id="Kff-ed-gdd"/>
|
||||
<action selector="didTapCancel" destination="VRk-wv-rhk" id="idg-Q7-qLu"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" enabled="NO" style="done" systemItem="save" id="rWg-hE-Ydl">
|
||||
<connections>
|
||||
<action selector="didTapSave:" destination="VRk-wv-rhk" id="Xee-qo-bQx"/>
|
||||
<action selector="didTapSave" destination="VRk-wv-rhk" id="fPE-i2-I06"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
@@ -518,7 +516,7 @@
|
||||
</connections>
|
||||
</navigationBar>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="xdn-EU-IMx">
|
||||
<rect key="frame" x="16" y="12" width="288" height="347.5"/>
|
||||
<rect key="frame" x="16" y="52" width="288" height="307.5"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Guy-Ra-fpS" userLabel="Title">
|
||||
<rect key="frame" x="0.0" y="0.0" width="288" height="40"/>
|
||||
@@ -562,7 +560,7 @@
|
||||
</connections>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ybL-UG-dwT" userLabel="Notes">
|
||||
<rect key="frame" x="0.0" y="48" width="288" height="200.5"/>
|
||||
<rect key="frame" x="0.0" y="48" width="288" height="160.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Notes" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QJp-6C-yoZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="288" height="24"/>
|
||||
@@ -572,7 +570,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NXU-yU-eST">
|
||||
<rect key="frame" x="0.0" y="24" width="288" height="176.5"/>
|
||||
<rect key="frame" x="0.0" y="24" width="288" height="136.5"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<string key="text">1. Line
|
||||
2. Line
|
||||
@@ -596,7 +594,7 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QiY-Mm-Dej" userLabel="Details">
|
||||
<rect key="frame" x="0.0" y="256.5" width="288" height="91"/>
|
||||
<rect key="frame" x="0.0" y="216.5" width="288" height="91"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Details" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="FR1-Nt-XuB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="288" height="24"/>
|
||||
@@ -605,9 +603,8 @@
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" fixedFrame="YES" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" bouncesZoom="NO" editable="NO" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pql-H5-k6U">
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" bouncesZoom="NO" editable="NO" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pql-H5-k6U">
|
||||
<rect key="frame" x="0.0" y="24" width="288" height="67"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<string key="text">Start: 1970-01-01 01:00
|
||||
End: 1970-01-01 02:00
|
||||
@@ -616,10 +613,23 @@ Duration: 60:00</string>
|
||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="LOr-e7-foG">
|
||||
<rect key="frame" x="268" y="46.5" width="20" height="22"/>
|
||||
<state key="normal" image="filter-clear"/>
|
||||
<connections>
|
||||
<action selector="didTapFilter" destination="VRk-wv-rhk" eventType="touchUpInside" id="5NH-qa-yMg"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="pql-H5-k6U" secondAttribute="trailing" id="44x-p3-qWK"/>
|
||||
<constraint firstItem="LOr-e7-foG" firstAttribute="trailing" secondItem="pql-H5-k6U" secondAttribute="trailing" id="5sr-Cf-h0c"/>
|
||||
<constraint firstItem="pql-H5-k6U" firstAttribute="top" secondItem="FR1-Nt-XuB" secondAttribute="bottom" id="As8-Px-t6G"/>
|
||||
<constraint firstItem="pql-H5-k6U" firstAttribute="leading" secondItem="QiY-Mm-Dej" secondAttribute="leading" id="ItB-cO-reV"/>
|
||||
<constraint firstItem="LOr-e7-foG" firstAttribute="centerY" secondItem="pql-H5-k6U" secondAttribute="centerY" id="OLu-MI-3sa"/>
|
||||
<constraint firstAttribute="height" priority="250" constant="91" id="or7-9o-FZb"/>
|
||||
<constraint firstAttribute="bottom" secondItem="pql-H5-k6U" secondAttribute="bottom" id="pbd-43-eN0"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
@@ -635,9 +645,9 @@ Duration: 60:00</string>
|
||||
<constraint firstItem="2yS-xK-Wac" firstAttribute="trailing" secondItem="fMa-Lq-tGz" secondAttribute="trailing" id="1io-bA-4p9"/>
|
||||
<constraint firstItem="2yS-xK-Wac" firstAttribute="leading" secondItem="fMa-Lq-tGz" secondAttribute="leading" id="Fv1-fO-22V"/>
|
||||
<constraint firstItem="xdn-EU-IMx" firstAttribute="leading" secondItem="fMa-Lq-tGz" secondAttribute="leading" constant="16" id="JuR-Ro-IPi"/>
|
||||
<constraint firstItem="xdn-EU-IMx" firstAttribute="top" secondItem="2yS-xK-Wac" secondAttribute="bottom" constant="12" id="Lec-83-aaD"/>
|
||||
<constraint firstItem="xdn-EU-IMx" firstAttribute="trailing" secondItem="fMa-Lq-tGz" secondAttribute="trailing" constant="-16" id="hhC-bL-G3S"/>
|
||||
<constraint firstItem="xdn-EU-IMx" firstAttribute="bottom" secondItem="fMa-Lq-tGz" secondAttribute="bottom" constant="-10" id="p7W-sr-Wch"/>
|
||||
<constraint firstItem="xdn-EU-IMx" firstAttribute="top" secondItem="2yS-xK-Wac" secondAttribute="bottom" constant="8" id="Lec-83-aaD"/>
|
||||
<constraint firstItem="fMa-Lq-tGz" firstAttribute="trailing" secondItem="xdn-EU-IMx" secondAttribute="trailing" constant="16" id="hhC-bL-G3S"/>
|
||||
<constraint firstItem="fMa-Lq-tGz" firstAttribute="bottom" secondItem="xdn-EU-IMx" secondAttribute="bottom" constant="10" id="p7W-sr-Wch"/>
|
||||
<constraint firstItem="2yS-xK-Wac" firstAttribute="top" secondItem="fMa-Lq-tGz" secondAttribute="top" id="yKh-gv-mgg"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="fMa-Lq-tGz"/>
|
||||
@@ -647,6 +657,7 @@ Duration: 60:00</string>
|
||||
<outlet property="appIcon" destination="rbW-pK-Kct" id="VlO-fG-y1a"/>
|
||||
<outlet property="appTitle" destination="Et0-8d-CId" id="HgD-oI-0J8"/>
|
||||
<outlet property="buttonCancel" destination="TGg-60-wZW" id="5Ej-7t-jaD"/>
|
||||
<outlet property="buttonFilter" destination="LOr-e7-foG" id="qUx-1k-xJK"/>
|
||||
<outlet property="buttonSave" destination="rWg-hE-Ydl" id="zfM-kx-erX"/>
|
||||
<outlet property="chooseAppTap" destination="Jab-q2-U9X" id="Tzv-lm-sUm"/>
|
||||
<outlet property="inputDetails" destination="pql-H5-k6U" id="NXm-8f-5E6"/>
|
||||
@@ -727,6 +738,7 @@ Duration: 60:00</string>
|
||||
<resources>
|
||||
<image name="LaunchIcon.png" width="128" height="128"/>
|
||||
<image name="detail-help" width="22" height="22"/>
|
||||
<image name="filter-clear" width="20" height="20"/>
|
||||
<image name="line-expand" width="20" height="20"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -20,15 +20,15 @@ struct AppStoreSearch {
|
||||
let developer, imageURL: String?
|
||||
}
|
||||
|
||||
static func search(_ term: String, _ closure: @escaping ([Result]?) -> Void) {
|
||||
static func search(_ term: String, _ closure: @escaping ([Result]?, Error?) -> Void) {
|
||||
URLSession.shared.dataTask(with: .init(url: .appStoreSearch(query: term))) { data, response, error in
|
||||
guard let data = data, error == nil,
|
||||
let response = response as? HTTPURLResponse,
|
||||
(200 ..< 300) ~= response.statusCode else {
|
||||
closure(nil)
|
||||
closure(nil, error ?? URLError(.badServerResponse))
|
||||
return
|
||||
}
|
||||
closure(jsonSearchToList(data))
|
||||
closure(jsonSearchToList(data), nil)
|
||||
}.resume()
|
||||
}
|
||||
|
||||
|
||||
@@ -98,9 +98,7 @@ struct BundleIcon {
|
||||
return img
|
||||
}
|
||||
|
||||
static func download(_ bundleId: String, urlStr: String, whenDone: @escaping () -> Void) {
|
||||
if let url = URL(string: urlStr) {
|
||||
url.download(to: local(bundleId), onSuccess: whenDone)
|
||||
}
|
||||
static func download(_ bundleId: String, url: URL, whenDone: @escaping () -> Void) -> URLSessionDownloadTask {
|
||||
return url.download(to: local(bundleId), onSuccess: whenDone)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
|
||||
private var searchActive: Bool = false
|
||||
var delegate: TVCAppSearchDelegate?
|
||||
|
||||
private var searchNo = 0
|
||||
private var searchError: Bool = false
|
||||
private var downloadQueue: [URLSessionDownloadTask] = []
|
||||
|
||||
@IBOutlet private var searchBar: UISearchBar!
|
||||
|
||||
override func viewDidLoad() {
|
||||
@@ -29,6 +33,18 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
private func showManualEntryAlert() {
|
||||
let alert = AskAlert(title: "App Name",
|
||||
text: "Be as descriptive as possible. Preferably use app bundle id if available. Alternatively use app name or a link to a public repository.",
|
||||
buttonText: "Set") {
|
||||
self.delegate?.appSearch(didSelect: "_manually", appName: $0.textFields?.first?.text, developer: nil)
|
||||
self.closeThis()
|
||||
}
|
||||
alert.addTextField { $0.placeholder = "com.apple.notes" }
|
||||
alert.presentIn(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func numberOfSections(in _: UITableView) -> Int {
|
||||
@@ -60,7 +76,13 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
|
||||
case 0:
|
||||
guard dataSource.count > 0, indexPath.row < dataSource.count else {
|
||||
if indexPath.row == 0 {
|
||||
cell.textLabel?.text = isLoading ? "Loading …" : "no results"
|
||||
if searchError {
|
||||
cell.textLabel?.text = "Error loading results"
|
||||
} else if isLoading {
|
||||
cell.textLabel?.text = "Loading …"
|
||||
} else {
|
||||
cell.textLabel?.text = "No results"
|
||||
}
|
||||
cell.isUserInteractionEnabled = false
|
||||
} else {
|
||||
cell.textLabel?.text = "Create manually …"
|
||||
@@ -84,13 +106,16 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
let sno = searchNo
|
||||
cell.imageView?.image = BundleIcon.image(bundleId) {
|
||||
guard let url = altLoadUrl else { return }
|
||||
BundleIcon.download(bundleId, urlStr: url) {
|
||||
guard let u = altLoadUrl, let url = URL(string: u) else { return }
|
||||
self.downloadQueue.append(BundleIcon.download(bundleId, url: url) {
|
||||
DispatchQueue.main.async {
|
||||
// make sure its the same request
|
||||
guard sno == self.searchNo else { return }
|
||||
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
cell.isUserInteractionEnabled = true
|
||||
cell.imageView?.layer.cornerRadius = 6.75
|
||||
@@ -103,14 +128,7 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
guard indexPath.row < dataSource.count else {
|
||||
let alert = AskAlert(title: "App Name",
|
||||
text: "Be as descriptive as possible. Preferably use app bundle id if available. Alternatively use app name or a link to a public repository.",
|
||||
buttonText: "Set") {
|
||||
self.delegate?.appSearch(didSelect: "_manually", appName: $0.textFields?.first?.text, developer: nil)
|
||||
self.closeThis()
|
||||
}
|
||||
alert.addTextField { $0.placeholder = "com.apple.notes" }
|
||||
alert.presentIn(self)
|
||||
showManualEntryAlert()
|
||||
return
|
||||
}
|
||||
let src = dataSource[indexPath.row]
|
||||
@@ -130,6 +148,8 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
|
||||
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performSearch), object: nil)
|
||||
isLoading = true
|
||||
tableView.reloadData()
|
||||
for x in downloadQueue { x.cancel() }
|
||||
downloadQueue = []
|
||||
if searchText.count > 0 {
|
||||
perform(#selector(performSearch), with: nil, afterDelay: 0.4)
|
||||
} else {
|
||||
@@ -140,18 +160,22 @@ class TVCAppSearch: UITableViewController, UISearchBarDelegate {
|
||||
/// Internal callback function for delayed text evaluation.
|
||||
/// This way we can avoid unnecessary searches while user is typing.
|
||||
@objc private func performSearch() {
|
||||
func setSource(_ newSource: [AppStoreSearch.Result], _ err: Bool) {
|
||||
searchNo += 1
|
||||
searchError = err
|
||||
dataSource = searchActive ? newSource : []
|
||||
tableView.reloadData()
|
||||
}
|
||||
isLoading = false
|
||||
let term = searchBar.text?.lowercased() ?? ""
|
||||
searchActive = term.count > 0
|
||||
guard searchActive else {
|
||||
dataSource = []
|
||||
tableView.reloadData()
|
||||
setSource([], false)
|
||||
return
|
||||
}
|
||||
AppStoreSearch.search(term) {
|
||||
self.dataSource = $0 ?? []
|
||||
AppStoreSearch.search(term) { source, error in
|
||||
DispatchQueue.main.async {
|
||||
self.tableView.reloadData()
|
||||
setSource(source ?? [], error != nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,11 @@ class TVCRecordingDetails: UITableViewController, EditActionsRemove {
|
||||
var noResults: Bool = false
|
||||
private lazy var isLongRecording: Bool = record.isLongTerm
|
||||
|
||||
@IBOutlet private var shareButton: UIBarButtonItem!
|
||||
|
||||
private var showRaw: Bool = false
|
||||
/// Sorted by `ts` in ascending order (oldest first)
|
||||
private lazy var dataSourceRaw: [DomainTsPair] = {
|
||||
let list = RecordingsDB.details(record)
|
||||
noResults = list.count == 0
|
||||
shareButton.isEnabled = !noResults
|
||||
return list
|
||||
}()
|
||||
/// Sorted by `count` (descending), then alphabetically
|
||||
@@ -115,7 +112,6 @@ class TVCRecordingDetails: UITableViewController, EditActionsRemove {
|
||||
}
|
||||
}
|
||||
noResults = dataSourceRaw.count == 0
|
||||
shareButton.isEnabled = !noResults
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ class TVCShareRecording : UITableViewController, UITextViewDelegate, VCEditTextD
|
||||
|
||||
// vars
|
||||
var record: Recording!
|
||||
private var shareNotes: Bool = false // opt-in
|
||||
private var shareNotes: Bool = true // green switch is more present
|
||||
private lazy var hasNotes: Bool = (self.record.notes != nil)
|
||||
private lazy var editedNotes: String = self.record.notes ?? ""
|
||||
private lazy var weekInYear: String = {
|
||||
@@ -86,6 +86,13 @@ class TVCShareRecording : UITableViewController, UITextViewDelegate, VCEditTextD
|
||||
}
|
||||
}
|
||||
|
||||
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
||||
if motion == .motionShake, let key = record.uploadkey {
|
||||
UIPasteboard.general.string = key
|
||||
banner(.ok, "Copied to clipboard", timeout: 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table Data Source
|
||||
|
||||
@@ -145,7 +152,7 @@ class TVCShareRecording : UITableViewController, UITextViewDelegate, VCEditTextD
|
||||
cell = tableView.dequeueReusableCell(withIdentifier: "shareKeyValueCell")!
|
||||
let src = dataSourceKeyValue[indexPath.row]
|
||||
cell.textLabel?.text = src.key
|
||||
let flag = shareNotes && indexPath.row == 4
|
||||
let flag = indexPath.row == 4 && shareNotes && hasNotes
|
||||
cell.detailTextLabel?.text = flag ? editedNotes : src.value
|
||||
case 3:
|
||||
cell = tableView.dequeueReusableCell(withIdentifier: "shareLogCell")!
|
||||
@@ -234,8 +241,8 @@ class TVCShareRecording : UITableViewController, UITextViewDelegate, VCEditTextD
|
||||
|
||||
// MARK: - Alerts & Banner
|
||||
|
||||
private func banner(_ style: NotificationBanner.Style, _ msg: String) {
|
||||
NotificationBanner(msg, style: style).present(in: self)
|
||||
private func banner(_ style: NotificationBanner.Style, _ msg: String, timeout: TimeInterval = 3) {
|
||||
NotificationBanner(msg, style: style).present(in: navigationController!, hideAfter: timeout)
|
||||
}
|
||||
|
||||
private func showAlertAvailableSoon(_ urlStr: String, when: Int?) {
|
||||
|
||||
@@ -16,8 +16,21 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
|
||||
@IBOutlet private var noteBottom: NSLayoutConstraint!
|
||||
|
||||
@IBOutlet private var chooseAppTap: UITapGestureRecognizer!
|
||||
@IBOutlet private var buttonFilter: UIButton!
|
||||
|
||||
override func viewDidLoad() {
|
||||
if deleteOnCancel { // aka newly created
|
||||
RecordingsDB.persist(record)
|
||||
if Prefs.RecordingReminder.Enabled {
|
||||
PushNotification.scheduleRecordingReminder(force: true)
|
||||
}
|
||||
buttonFilter.isHidden = true
|
||||
// mark as destructive
|
||||
buttonCancel.tintColor = .systemRed
|
||||
if #available(iOS 13.0, *) {
|
||||
isModalInPresentation = true
|
||||
}
|
||||
}
|
||||
if record.isLongTerm {
|
||||
appId = nil
|
||||
appIcon.image = nil
|
||||
@@ -44,12 +57,6 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
|
||||
Duration: \(TimeFormat.from(record.duration))
|
||||
"""
|
||||
validateSaveButton()
|
||||
if deleteOnCancel { // mark as destructive
|
||||
buttonCancel.tintColor = .systemRed
|
||||
if #available(iOS 13.0, *) {
|
||||
isModalInPresentation = true
|
||||
}
|
||||
}
|
||||
UIResponder.keyboardWillShowNotification.observe(call: #selector(keyboardWillShow), on: self)
|
||||
UIResponder.keyboardWillHideNotification.observe(call: #selector(keyboardWillHide), on: self)
|
||||
}
|
||||
@@ -62,12 +69,9 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
|
||||
|
||||
// MARK: Save & Cancel Buttons
|
||||
|
||||
@IBAction func didTapSave(_ sender: UIBarButtonItem) {
|
||||
let newlyCreated = deleteOnCancel
|
||||
if newlyCreated {
|
||||
// if remains true, `viewDidDisappear` will delete the record
|
||||
deleteOnCancel = false
|
||||
}
|
||||
@IBAction func didTapSave() {
|
||||
// if remains true, `viewDidDisappear` will delete the record
|
||||
deleteOnCancel = false
|
||||
QLog.Debug("updating record #\(record.id)")
|
||||
if let id = appId, id != "" {
|
||||
record.appId = id
|
||||
@@ -81,16 +85,10 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
|
||||
record.notes = (inputNotes.text == "") ? nil : inputNotes.text
|
||||
dismiss(animated: true) {
|
||||
RecordingsDB.update(self.record)
|
||||
if newlyCreated {
|
||||
RecordingsDB.persist(self.record)
|
||||
if Prefs.RecordingReminder.Enabled {
|
||||
PushNotification.scheduleRecordingReminder(force: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func didTapCancel(_ sender: UIBarButtonItem) {
|
||||
@IBAction func didTapCancel() {
|
||||
QLog.Debug("discard edit of record #\(record.id)")
|
||||
dismiss(animated: true)
|
||||
}
|
||||
@@ -103,6 +101,16 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func didTapFilter() {
|
||||
if buttonSave.isEnabled {
|
||||
NotificationBanner("Filter set", style: .ok).present(in: self, hideAfter: 1)
|
||||
} else {
|
||||
(presentingViewController as? TBCMain)?.openTab(0)
|
||||
didTapCancel()
|
||||
}
|
||||
VCDateFilter.setFilter(range: record.start, to: record.stop)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Handle Keyboard & Notes Frame
|
||||
|
||||
|
||||
@@ -10,8 +10,10 @@ class VCRecordings: UIViewController, UINavigationControllerDelegate {
|
||||
@IBOutlet private var runningView: UIView!
|
||||
@IBOutlet private var timeLabel: UILabel!
|
||||
@IBOutlet private var stopButton: UIButton!
|
||||
@IBOutlet private var startSegment: UISegmentedControl!
|
||||
|
||||
override func viewDidLoad() {
|
||||
startSegment.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.sysLink], for: .normal)
|
||||
timeLabel.font = timeLabel.font.monoSpace()
|
||||
if let ongoing = RecordingsDB.getCurrent() {
|
||||
currentRecording = ongoing
|
||||
|
||||
@@ -70,7 +70,7 @@ class TVCOccurrenceContext: UITableViewController {
|
||||
private var copyDomain: String? = nil
|
||||
|
||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
if cellMenu.start(tableView, indexPath) {
|
||||
if !firstOrLast(indexPath.row), cellMenu.start(tableView, indexPath) {
|
||||
copyDomain = cellMenu.getSelected(dataSource)?.domain
|
||||
self.becomeFirstResponder()
|
||||
}
|
||||
|
||||
@@ -125,4 +125,11 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate {
|
||||
NotifyDateFilterChanged.post()
|
||||
}
|
||||
}
|
||||
|
||||
static func setFilter(range from: Timestamp?, to: Timestamp?) {
|
||||
Prefs.DateFilter.Kind = .ABRange
|
||||
Prefs.DateFilter.RangeA = from
|
||||
Prefs.DateFilter.RangeB = to
|
||||
NotifyDateFilterChanged.post()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ class TBCMain: UITabBarController {
|
||||
}
|
||||
|
||||
extension TBCMain {
|
||||
/// Open tab and pop to root view controller.
|
||||
@discardableResult func openTab(_ index: Int) -> UIViewController? {
|
||||
selectedIndex = index
|
||||
guard let nav = selectedViewController as? UINavigationController else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# What are Recordings?
|
||||
|
||||
Similar to the default logging, recordings will intercept every request and log it for later review. App recordings are usually 3 – 5 minutes long and cover a single application. You can utilize recordings for App analysis or to get a ground truth on background traffic.
|
||||
Similar to the default logging, recordings will intercept every request and log it for later review. App recordings are usually 1 – 4 minutes long and cover a single application. You can utilize recordings for App analysis or to get a ground truth on background traffic.
|
||||
|
||||
Optionally, you can help us by providing your app specific recordings. Together with your findings we can create a community driven privacy monitor. The research results will help you and others avoid Apps that unnecessarily share data with third-party providers.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# How to record?
|
||||
|
||||
Before you begin, there are two types of recordings: app specific recordings and general background activity. The former are usually 3 – 5 minutes long, the latter need to be at least an hour long.
|
||||
Before you begin, there are two types of recordings: app specific recordings and general background activity. The former are usually less than 5 minutes long, the latter must be at least an hour long.
|
||||
|
||||
### Important notice
|
||||
|
||||
|
||||
Reference in New Issue
Block a user