Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d68e4ec869 | ||
|
|
762263bfbd | ||
|
|
b1cddc796e | ||
|
|
77e20f31f5 | ||
|
|
0175f5390e | ||
|
|
effc305b86 | ||
|
|
c1fe258b0d | ||
|
|
36a8f0b97b | ||
|
|
33b9cab8a8 | ||
|
|
b88874b38b | ||
|
|
f55f3ea32d | ||
|
|
c843bd76a2 | ||
|
|
4dd2339ed8 | ||
|
|
280526bef4 | ||
|
|
34caffd4a7 | ||
|
|
9e19b457e2 | ||
|
|
e6846953b7 | ||
|
|
6d78aeac7b | ||
|
|
5d94fe3a0d | ||
|
|
fb680d669b | ||
|
|
6409e5eaf3 | ||
|
|
39ca9dbdb1 | ||
|
|
27ab2a621a | ||
|
|
3f572eeb15 | ||
|
|
e83540d5de | ||
|
|
847556bec1 | ||
|
|
42b045fb85 | ||
|
|
35a211f87f | ||
|
|
d2fa67e0e3 | ||
|
|
b8660c9a35 | ||
|
|
8cd3f7fb3a | ||
|
|
2ee0272a05 | ||
|
|
4ae82fc763 | ||
|
|
aac42d7eff | ||
|
|
8bb77ef741 | ||
|
|
ff4218981f | ||
|
|
7b7c5f3d9a | ||
|
|
1c203e39c3 | ||
|
|
7dbf21d564 | ||
|
|
8fcb5ad874 | ||
|
|
b4bf705b7f | ||
|
|
69d8321180 | ||
|
|
b03daeca66 | ||
|
|
c502484bcf | ||
|
|
448d69c6d8 | ||
|
|
42aa7cf926 | ||
|
|
52fa2e460e | ||
|
|
8855ae754a | ||
|
|
908a909c87 | ||
|
|
41aee797a9 | ||
|
|
685f636d5b | ||
|
|
4af56b0cb1 | ||
|
|
a3973c7e9a |
@@ -70,6 +70,14 @@
|
||||
545DDDD124436983003B6544 /* QuickUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDD024436983003B6544 /* QuickUI.swift */; };
|
||||
545DDDD424466D37003B6544 /* AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDD324466D37003B6544 /* AutoLayout.swift */; };
|
||||
546063E523FEFAFE008F505A /* DBCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7562223D7B2DC008F0C41 /* DBCore.swift */; };
|
||||
54686A7624F8062C0084934D /* NotificationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54686A7524F8062C0084934D /* NotificationBanner.swift */; };
|
||||
54686A8524FD0A3F0084934D /* tut-recording-howto.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8424FD0A3F0084934D /* tut-recording-howto.md */; };
|
||||
54686A8724FD27AA0084934D /* TinyMarkdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54686A8624FD26410084934D /* TinyMarkdown.swift */; };
|
||||
54686A8D24FD428C0084934D /* tut-welcome-1.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8824FD31580084934D /* tut-welcome-1.md */; };
|
||||
54686A8E24FD42950084934D /* tut-welcome-2.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8B24FD3F180084934D /* tut-welcome-2.md */; };
|
||||
54686A8F24FD42950084934D /* tut-recording-1.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8A24FD3F100084934D /* tut-recording-1.md */; };
|
||||
54686A9024FD42950084934D /* tut-recording-2.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8924FD31630084934D /* tut-recording-2.md */; };
|
||||
54686A9124FD42950084934D /* tut-cooccurrence.md in Resources */ = {isa = PBXBuildFile; fileRef = 54686A8C24FD3F630084934D /* tut-cooccurrence.md */; };
|
||||
54751E512423955100168273 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54751E502423955000168273 /* URL.swift */; };
|
||||
54751E522423955100168273 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54751E502423955000168273 /* URL.swift */; };
|
||||
54953E3323DC752E0054345C /* DBCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7562223D7B2DC008F0C41 /* DBCore.swift */; };
|
||||
@@ -77,7 +85,12 @@
|
||||
54953E6123E0D69A0054345C /* TVCHosts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54953E6023E0D69A0054345C /* TVCHosts.swift */; };
|
||||
54953E6F23E44CD00054345C /* TVCHostDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54953E6E23E44CD00054345C /* TVCHostDetails.swift */; };
|
||||
54953E7123E473F10054345C /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 54953E7023E473F10054345C /* Settings.bundle */; };
|
||||
549A96D62501198400C565FA /* VCEditText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549A96D52501198400C565FA /* VCEditText.swift */; };
|
||||
549A96DA250419B200C565FA /* CoOccurrence.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 549A96D8250419B200C565FA /* CoOccurrence.storyboard */; };
|
||||
549D6ED524D5BFDB0032E498 /* TVCAppSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549D6ED424D5BFDB0032E498 /* TVCAppSearch.swift */; };
|
||||
549ECD9D24A7AD550097571C /* CustomAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549ECD9C24A7AD550097571C /* CustomAlert.swift */; };
|
||||
54A0CC0924E30C56009B5EC1 /* Recordings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54A0CC0724E30C56009B5EC1 /* Recordings.storyboard */; };
|
||||
54A0CC0C24E30D6F009B5EC1 /* Requests.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54A0CC0A24E30D6F009B5EC1 /* Requests.storyboard */; };
|
||||
54B34594240E6343004C53CC /* TVCFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B34593240E6343004C53CC /* TVCFilter.swift */; };
|
||||
54B34596240F0513004C53CC /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B34595240F0513004C53CC /* TableView.swift */; };
|
||||
54B345A6241BB982004C53CC /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345A5241BB982004C53CC /* Notifications.swift */; };
|
||||
@@ -85,7 +98,7 @@
|
||||
54B345AB241BBA5B004C53CC /* AlertSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345AA241BBA5B004C53CC /* AlertSheet.swift */; };
|
||||
54B345AD241BBB00004C53CC /* DBExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345AC241BBB00004C53CC /* DBExtensions.swift */; };
|
||||
54B345B0242264F8004C53CC /* third-level.txt in Resources */ = {isa = PBXBuildFile; fileRef = 54B345AF242264F8004C53CC /* third-level.txt */; };
|
||||
54C056DB23E9E36E00214A3F /* AppInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C056DA23E9E36E00214A3F /* AppInfoType.swift */; };
|
||||
54C056DB23E9E36E00214A3F /* AppStoreSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C056DA23E9E36E00214A3F /* AppStoreSearch.swift */; };
|
||||
54C056DD23E9EEF700214A3F /* BundleIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C056DC23E9EEF700214A3F /* BundleIcon.swift */; };
|
||||
54CA01D32426B23D003A5E04 /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01D22426B23D003A5E04 /* Resolver.swift */; };
|
||||
54CA01D52426B252003A5E04 /* SafeDict.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01D42426B251003A5E04 /* SafeDict.swift */; };
|
||||
@@ -169,6 +182,7 @@
|
||||
54CA02C42426DCCD003A5E04 /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02C02426DCCD003A5E04 /* GCDAsyncUdpSocket.m */; };
|
||||
54CE8BC424B1ED2100CC1756 /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CE8BC324B1ED2100CC1756 /* PushNotification.swift */; };
|
||||
54CE8BC524B1ED2100CC1756 /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CE8BC324B1ED2100CC1756 /* PushNotification.swift */; };
|
||||
54CFE86824E3F401001687DD /* TVCShareRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFE86724E3F401001687DD /* TVCShareRecording.swift */; };
|
||||
54D8B97A246C9F2000EB2414 /* FilterPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B979246C9F2000EB2414 /* FilterPipeline.swift */; };
|
||||
54D8B97C2471A7E000EB2414 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B97B2471A7E000EB2414 /* String.swift */; };
|
||||
54D8B97E2471B88900EB2414 /* DBCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B97D2471B88900EB2414 /* DBCommon.swift */; };
|
||||
@@ -264,13 +278,26 @@
|
||||
545DDDCE243E6267003B6544 /* TutorialSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialSheet.swift; sourceTree = "<group>"; };
|
||||
545DDDD024436983003B6544 /* QuickUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickUI.swift; sourceTree = "<group>"; };
|
||||
545DDDD324466D37003B6544 /* AutoLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoLayout.swift; sourceTree = "<group>"; };
|
||||
54686A7524F8062C0084934D /* NotificationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationBanner.swift; sourceTree = "<group>"; };
|
||||
54686A8424FD0A3F0084934D /* tut-recording-howto.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "tut-recording-howto.md"; sourceTree = "<group>"; };
|
||||
54686A8624FD26410084934D /* TinyMarkdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TinyMarkdown.swift; sourceTree = "<group>"; };
|
||||
54686A8824FD31580084934D /* tut-welcome-1.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "tut-welcome-1.md"; sourceTree = "<group>"; };
|
||||
54686A8924FD31630084934D /* tut-recording-2.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "tut-recording-2.md"; sourceTree = "<group>"; };
|
||||
54686A8A24FD3F100084934D /* tut-recording-1.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "tut-recording-1.md"; sourceTree = "<group>"; };
|
||||
54686A8B24FD3F180084934D /* tut-welcome-2.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "tut-welcome-2.md"; sourceTree = "<group>"; };
|
||||
54686A8C24FD3F630084934D /* tut-cooccurrence.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "tut-cooccurrence.md"; sourceTree = "<group>"; };
|
||||
54751E502423955000168273 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
|
||||
548B1F9423D338EC005B047C /* main.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = main.entitlements; sourceTree = "<group>"; };
|
||||
54953E5E23DEBE840054345C /* TVCDomains.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCDomains.swift; sourceTree = "<group>"; };
|
||||
54953E6023E0D69A0054345C /* TVCHosts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCHosts.swift; sourceTree = "<group>"; };
|
||||
54953E6E23E44CD00054345C /* TVCHostDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCHostDetails.swift; sourceTree = "<group>"; };
|
||||
54953E7023E473F10054345C /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
549A96D52501198400C565FA /* VCEditText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCEditText.swift; sourceTree = "<group>"; };
|
||||
549A96D9250419B200C565FA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/CoOccurrence.storyboard; sourceTree = "<group>"; };
|
||||
549D6ED424D5BFDB0032E498 /* TVCAppSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCAppSearch.swift; sourceTree = "<group>"; };
|
||||
549ECD9C24A7AD550097571C /* CustomAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAlert.swift; sourceTree = "<group>"; };
|
||||
54A0CC0824E30C56009B5EC1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Recordings.storyboard; sourceTree = "<group>"; };
|
||||
54A0CC0B24E30D6F009B5EC1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Requests.storyboard; sourceTree = "<group>"; };
|
||||
54B34593240E6343004C53CC /* TVCFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCFilter.swift; sourceTree = "<group>"; };
|
||||
54B34595240F0513004C53CC /* TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; };
|
||||
54B345A5241BB982004C53CC /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
|
||||
@@ -279,7 +306,7 @@
|
||||
54B345AC241BBB00004C53CC /* DBExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBExtensions.swift; sourceTree = "<group>"; };
|
||||
54B345AF242264F8004C53CC /* third-level.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "third-level.txt"; sourceTree = "<group>"; };
|
||||
54B7562223D7B2DC008F0C41 /* DBCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBCore.swift; sourceTree = "<group>"; };
|
||||
54C056DA23E9E36E00214A3F /* AppInfoType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoType.swift; sourceTree = "<group>"; };
|
||||
54C056DA23E9E36E00214A3F /* AppStoreSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreSearch.swift; sourceTree = "<group>"; };
|
||||
54C056DC23E9EEF700214A3F /* BundleIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIcon.swift; sourceTree = "<group>"; };
|
||||
54CA00D62426A803003A5E04 /* CocoaAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CocoaAsyncSocket.h; sourceTree = "<group>"; };
|
||||
54CA01D22426B23D003A5E04 /* Resolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; };
|
||||
@@ -365,6 +392,7 @@
|
||||
54CA02C12426DCCD003A5E04 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = "<group>"; };
|
||||
54CA02C22426DCCD003A5E04 /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncUdpSocket.h; sourceTree = "<group>"; };
|
||||
54CE8BC324B1ED2100CC1756 /* PushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotification.swift; sourceTree = "<group>"; };
|
||||
54CFE86724E3F401001687DD /* TVCShareRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCShareRecording.swift; sourceTree = "<group>"; };
|
||||
54D8B979246C9F2000EB2414 /* FilterPipeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterPipeline.swift; sourceTree = "<group>"; };
|
||||
54D8B97B2471A7E000EB2414 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||
54D8B97D2471B88900EB2414 /* DBCommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBCommon.swift; sourceTree = "<group>"; };
|
||||
@@ -430,8 +458,12 @@
|
||||
children = (
|
||||
540E677F242D2CF100871BBE /* VCRecordings.swift */,
|
||||
540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */,
|
||||
540E67812433483D00871BBE /* VCEditRecording.swift */,
|
||||
5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */,
|
||||
54CFE86724E3F401001687DD /* TVCShareRecording.swift */,
|
||||
549A96D52501198400C565FA /* VCEditText.swift */,
|
||||
540E67812433483D00871BBE /* VCEditRecording.swift */,
|
||||
549D6ED424D5BFDB0032E498 /* TVCAppSearch.swift */,
|
||||
54B345B12422E029004C53CC /* App Icons */,
|
||||
);
|
||||
path = Recordings;
|
||||
sourceTree = "<group>";
|
||||
@@ -482,10 +514,7 @@
|
||||
540C6454240D5BAE00E948F9 /* Requests */,
|
||||
540E677E242D2CD200871BBE /* Recordings */,
|
||||
540C6455240D5BD200E948F9 /* Settings */,
|
||||
54B345B12422E029004C53CC /* unused */,
|
||||
541AC5E02399498B00A769D7 /* LaunchScreen.storyboard */,
|
||||
541AC5DB2399498A00A769D7 /* Main.storyboard */,
|
||||
543078C124B60F3B00278F2D /* Settings.storyboard */,
|
||||
54A0CC0D24E314B6009B5EC1 /* GUI */,
|
||||
541AC5DE2399498B00A769D7 /* Assets.xcassets */,
|
||||
541AC5E32399498B00A769D7 /* Info.plist */,
|
||||
54953E7023E473F10054345C /* Settings.bundle */,
|
||||
@@ -505,6 +534,7 @@
|
||||
542E2A9B24051F79001462DC /* media */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54686A8324FD0A3F0084934D /* tutorials */,
|
||||
5430789E24B5E10E00278F2D /* sounds */,
|
||||
541A957523E602DF00C09C19 /* LaunchIcon.png */,
|
||||
54B345AF242264F8004C53CC /* third-level.txt */,
|
||||
@@ -551,16 +581,44 @@
|
||||
54E67E4524A8B0FE0025D261 /* PrefsShared.swift */,
|
||||
545DDDD024436983003B6544 /* QuickUI.swift */,
|
||||
545DDDCE243E6267003B6544 /* TutorialSheet.swift */,
|
||||
54686A8624FD26410084934D /* TinyMarkdown.swift */,
|
||||
54D8B979246C9F2000EB2414 /* FilterPipeline.swift */,
|
||||
54448A3124899A4000771C96 /* SearchBarManager.swift */,
|
||||
549ECD9C24A7AD550097571C /* CustomAlert.swift */,
|
||||
541FC47524A12D01009154D8 /* IBViews.swift */,
|
||||
5404AEEC24A95F3F003B2F54 /* SlideInAnimation.swift */,
|
||||
541075D024CDBA0000D6F1BF /* ThrottledBatchQueue.swift */,
|
||||
54686A7524F8062C0084934D /* NotificationBanner.swift */,
|
||||
);
|
||||
path = "Common Classes";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54686A8324FD0A3F0084934D /* tutorials */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54686A8824FD31580084934D /* tut-welcome-1.md */,
|
||||
54686A8B24FD3F180084934D /* tut-welcome-2.md */,
|
||||
54686A8A24FD3F100084934D /* tut-recording-1.md */,
|
||||
54686A8924FD31630084934D /* tut-recording-2.md */,
|
||||
54686A8424FD0A3F0084934D /* tut-recording-howto.md */,
|
||||
54686A8C24FD3F630084934D /* tut-cooccurrence.md */,
|
||||
);
|
||||
path = tutorials;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54A0CC0D24E314B6009B5EC1 /* GUI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
541AC5E02399498B00A769D7 /* LaunchScreen.storyboard */,
|
||||
541AC5DB2399498A00A769D7 /* Main.storyboard */,
|
||||
54A0CC0A24E30D6F009B5EC1 /* Requests.storyboard */,
|
||||
549A96D8250419B200C565FA /* CoOccurrence.storyboard */,
|
||||
54A0CC0724E30C56009B5EC1 /* Recordings.storyboard */,
|
||||
543078C124B60F3B00278F2D /* Settings.storyboard */,
|
||||
);
|
||||
path = GUI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54B3459A2415651C004C53CC /* DB */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -593,13 +651,13 @@
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54B345B12422E029004C53CC /* unused */ = {
|
||||
54B345B12422E029004C53CC /* App Icons */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54C056DC23E9EEF700214A3F /* BundleIcon.swift */,
|
||||
54C056DA23E9E36E00214A3F /* AppInfoType.swift */,
|
||||
54C056DA23E9E36E00214A3F /* AppStoreSearch.swift */,
|
||||
);
|
||||
path = unused;
|
||||
path = "App Icons";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54CA00D52426A7F2003A5E04 /* robbiehanson-CocoaAsyncSocket */ = {
|
||||
@@ -933,21 +991,30 @@
|
||||
files = (
|
||||
543078AC24B5E12500278F2D /* typewriter2.caf in Resources */,
|
||||
54953E7123E473F10054345C /* Settings.bundle in Resources */,
|
||||
54686A9024FD42950084934D /* tut-recording-2.md in Resources */,
|
||||
543078B024B5E12500278F2D /* plop2.caf in Resources */,
|
||||
541AC5E22399498B00A769D7 /* LaunchScreen.storyboard in Resources */,
|
||||
54A0CC0924E30C56009B5EC1 /* Recordings.storyboard in Resources */,
|
||||
54686A8D24FD428C0084934D /* tut-welcome-1.md in Resources */,
|
||||
543078B824B5E12500278F2D /* wood2.caf in Resources */,
|
||||
543078BE24B5E12500278F2D /* drum2.caf in Resources */,
|
||||
543078B424B5E12500278F2D /* snap1.caf in Resources */,
|
||||
541AC5DF2399498B00A769D7 /* Assets.xcassets in Resources */,
|
||||
543078AE24B5E12500278F2D /* wood1.caf in Resources */,
|
||||
54686A8524FD0A3F0084934D /* tut-recording-howto.md in Resources */,
|
||||
541AC5DD2399498A00A769D7 /* Main.storyboard in Resources */,
|
||||
54B345B0242264F8004C53CC /* third-level.txt in Resources */,
|
||||
54686A8F24FD42950084934D /* tut-recording-1.md in Resources */,
|
||||
543078BA24B5E12500278F2D /* typewriter1.caf in Resources */,
|
||||
543078B224B5E12500278F2D /* plop1.caf in Resources */,
|
||||
543078B624B5E12500278F2D /* drum1.caf in Resources */,
|
||||
543078BC24B5E12500278F2D /* clock.caf in Resources */,
|
||||
543078C324B60F3B00278F2D /* Settings.storyboard in Resources */,
|
||||
543078AA24B5E12500278F2D /* snap2.caf in Resources */,
|
||||
54A0CC0C24E30D6F009B5EC1 /* Requests.storyboard in Resources */,
|
||||
549A96DA250419B200C565FA /* CoOccurrence.storyboard in Resources */,
|
||||
54686A9124FD42950084934D /* tut-cooccurrence.md in Resources */,
|
||||
54686A8E24FD42950084934D /* tut-welcome-2.md in Resources */,
|
||||
541A957623E602DF00C09C19 /* LaunchIcon.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -995,6 +1062,7 @@
|
||||
54B345A6241BB982004C53CC /* Notifications.swift in Sources */,
|
||||
54448A2E2486464F00771C96 /* Array.swift in Sources */,
|
||||
54E67E4B24A8C6370025D261 /* GlassVPN.swift in Sources */,
|
||||
54686A7624F8062C0084934D /* NotificationBanner.swift in Sources */,
|
||||
541FC47824A1453F009154D8 /* VCCoOccurrence.swift in Sources */,
|
||||
54B345AB241BBA5B004C53CC /* AlertSheet.swift in Sources */,
|
||||
541DCA6124A6B0F6005F1A4B /* Color.swift in Sources */,
|
||||
@@ -1009,9 +1077,11 @@
|
||||
544F912024A67EC5001D4B00 /* TVCOccurrenceContext.swift in Sources */,
|
||||
54448A30248647D900771C96 /* Time.swift in Sources */,
|
||||
54953E6123E0D69A0054345C /* TVCHosts.swift in Sources */,
|
||||
549D6ED524D5BFDB0032E498 /* TVCAppSearch.swift in Sources */,
|
||||
54CFE86824E3F401001687DD /* TVCShareRecording.swift in Sources */,
|
||||
54751E512423955100168273 /* URL.swift in Sources */,
|
||||
54953E5F23DEBE840054345C /* TVCDomains.swift in Sources */,
|
||||
54C056DB23E9E36E00214A3F /* AppInfoType.swift in Sources */,
|
||||
54C056DB23E9E36E00214A3F /* AppStoreSearch.swift in Sources */,
|
||||
54D8B98624796E9900EB2414 /* GroupedDomainDataSource.swift in Sources */,
|
||||
541075CE24C9D43A00D6F1BF /* UNNotification.swift in Sources */,
|
||||
54953E6F23E44CD00054345C /* TVCHostDetails.swift in Sources */,
|
||||
@@ -1021,7 +1091,9 @@
|
||||
542E2A982404973F001462DC /* TBCMain.swift in Sources */,
|
||||
54448A3224899A4000771C96 /* SearchBarManager.swift in Sources */,
|
||||
5458EBC0243A3F2200CFEB15 /* TVCRecordingDetails.swift in Sources */,
|
||||
549A96D62501198400C565FA /* VCEditText.swift in Sources */,
|
||||
541FC9872497D81C00962623 /* TheGreatDestroyer.swift in Sources */,
|
||||
54686A8724FD27AA0084934D /* TinyMarkdown.swift in Sources */,
|
||||
5412F8EE24571B8200A63D7A /* VCDateFilter.swift in Sources */,
|
||||
5412FCC424C628FA000DE429 /* TVCConnectionAlerts.swift in Sources */,
|
||||
543078C924B75CEA00278F2D /* PushNotificationAppOnly.swift in Sources */,
|
||||
@@ -1171,6 +1243,30 @@
|
||||
name = Settings.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
549A96D8250419B200C565FA /* CoOccurrence.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
549A96D9250419B200C565FA /* Base */,
|
||||
);
|
||||
name = CoOccurrence.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54A0CC0724E30C56009B5EC1 /* Recordings.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
54A0CC0824E30C56009B5EC1 /* Base */,
|
||||
);
|
||||
name = Recordings.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54A0CC0A24E30D6F009B5EC1 /* Requests.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
54A0CC0B24E30D6F009B5EC1 /* Base */,
|
||||
);
|
||||
name = Requests.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -1301,7 +1397,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
CURRENT_PROJECT_VERSION = 33;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1320,7 +1416,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
CURRENT_PROJECT_VERSION = 33;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1339,7 +1435,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
CURRENT_PROJECT_VERSION = 33;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
@@ -1357,7 +1453,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 26;
|
||||
CURRENT_PROJECT_VERSION = 33;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import NetworkExtension
|
||||
|
||||
let connectMessage: Data = "CONNECT".data(using: .ascii)!
|
||||
let swcdUserAgent: Data = "User-Agent: swcd".data(using: .ascii)!
|
||||
fileprivate var hook : GlassVPNHook!
|
||||
|
||||
// MARK: ObserverFactory
|
||||
@@ -15,8 +17,20 @@ class LDObserverFactory: ObserverFactory {
|
||||
override func signal(_ event: ProxySocketEvent) {
|
||||
switch event {
|
||||
case .receivedRequest(let session, let socket):
|
||||
if socket.isCancelled ||
|
||||
(hook.forceDisconnectUnresolvable && session.ipAddress.isEmpty) {
|
||||
hook.silentlyPrevented(session.host)
|
||||
socket.forceDisconnect()
|
||||
return
|
||||
}
|
||||
let kill = hook.processDNSRequest(session.host)
|
||||
if kill { socket.forceDisconnect() }
|
||||
case .readData(let data, on: let socket):
|
||||
if hook.forceDisconnectSWCD,
|
||||
data.starts(with: connectMessage),
|
||||
data.range(of: swcdUserAgent) != nil {
|
||||
socket.disconnect()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -39,11 +39,11 @@ That means, AppCheck does not have to be active in the foreground all the time.
|
||||
- What other domains occur often at the same time?
|
||||
- What happened immediately before or after the action?
|
||||
- Export results for custom analysis
|
||||
|
||||
**… and soon:**
|
||||
|
||||
- Alert Monitor & reminder
|
||||
- Participate in privacy research
|
||||
- Contribute your results
|
||||
- See what others have unveiled
|
||||
- How much traffic does this app produce?
|
||||
|
||||
|
||||
<sup>1</sup> Due to technical limitations, recordings can not be restricted to a single application. Remember to force-quit all other applications before starting a recording.
|
||||
@@ -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.
|
||||
|
||||
@@ -33,3 +33,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
// This is a known issue and tolerated.
|
||||
}
|
||||
}
|
||||
|
||||
extension URL {
|
||||
@discardableResult func open() -> Bool { UIApplication.shared.openURL(self) }
|
||||
}
|
||||
|
||||
BIN
main/Assets.xcassets/.DS_Store
vendored
26
main/Assets.xcassets/circle-check.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "img.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "img@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "img@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
main/Assets.xcassets/circle-check.imageset/img.png
vendored
Normal file
|
After Width: | Height: | Size: 247 B |
BIN
main/Assets.xcassets/circle-check.imageset/img@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 423 B |
BIN
main/Assets.xcassets/circle-check.imageset/img@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 572 B |
26
main/Assets.xcassets/circle-x.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "img.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "img@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "img@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
main/Assets.xcassets/circle-x.imageset/img.png
vendored
Normal file
|
After Width: | Height: | Size: 235 B |
BIN
main/Assets.xcassets/circle-x.imageset/img@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 400 B |
BIN
main/Assets.xcassets/circle-x.imageset/img@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 530 B |
26
main/Assets.xcassets/detail-help.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "img.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "img@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "img@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
main/Assets.xcassets/detail-help.imageset/img.png
vendored
Normal file
|
After Width: | Height: | Size: 322 B |
BIN
main/Assets.xcassets/detail-help.imageset/img@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 510 B |
BIN
main/Assets.xcassets/detail-help.imageset/img@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 701 B |
@@ -9,7 +9,7 @@ class CustomAlert<CustomView: UIView>: UIViewController {
|
||||
private var callback: ((CustomView) -> Void)?
|
||||
|
||||
/// Default: `[Cancel, Save]`
|
||||
let buttonsBar: UIStackView = {
|
||||
lazy var buttonsBar: UIStackView = {
|
||||
let cancel = QuickUI.button("Cancel", target: self, action: #selector(didTapCancel))
|
||||
let save = QuickUI.button("Save", target: self, action: #selector(didTapSave))
|
||||
save.titleLabel?.font = save.titleLabel?.font.bold()
|
||||
|
||||
71
main/Common Classes/NotificationBanner.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
import UIKit
|
||||
|
||||
struct NotificationBanner {
|
||||
enum Style {
|
||||
case fail, ok
|
||||
}
|
||||
|
||||
let view: UIView
|
||||
|
||||
init(_ msg: String, style: Style) {
|
||||
let bg, fg: UIColor
|
||||
let imgName: String
|
||||
switch style {
|
||||
case .fail:
|
||||
bg = .systemRed
|
||||
fg = UIColor.black.withAlphaComponent(0.80)
|
||||
imgName = "circle-x"
|
||||
case .ok:
|
||||
bg = .systemGreen
|
||||
fg = UIColor.black.withAlphaComponent(0.65)
|
||||
imgName = "circle-check"
|
||||
}
|
||||
view = UIView()
|
||||
view.backgroundColor = bg
|
||||
let lbl = QuickUI.label(msg, style: .callout)
|
||||
lbl.textColor = fg
|
||||
lbl.numberOfLines = 0
|
||||
lbl.font = lbl.font.bold()
|
||||
let img = QuickUI.image(UIImage(named: imgName))
|
||||
img.tintColor = fg
|
||||
view.addSubview(lbl)
|
||||
view.addSubview(img)
|
||||
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)
|
||||
lbl.bottomAnchor =<= view.bottomAnchor - 8 | .init(rawValue: 999)
|
||||
}
|
||||
|
||||
/// Animate header banner from the top of the view. Show for `delay` seconds and then hide again.
|
||||
/// - Parameter onClose: Run after the close animation finishes.
|
||||
func present(in vc: UIViewController, hideAfter delay: TimeInterval = 3, onClose: (() -> Void)? = nil) {
|
||||
vc.view.addSubview(view)
|
||||
view.anchor([.leading, .trailing], to: vc.view!)
|
||||
view.widthAnchor =&= vc.view!.widthAnchor // Bug? left-right is not sufficient
|
||||
vc.view.layoutIfNeeded() // sets the height
|
||||
let h = view.frame.height
|
||||
let constraint = view.topAnchor =&= vc.view.topAnchor - h
|
||||
vc.view.layoutIfNeeded() // hide view
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
constraint.constant = 0
|
||||
vc.view.layoutIfNeeded() // animate view
|
||||
UIView.animate(withDuration: 0.3, delay: delay, options: .curveLinear, animations: {
|
||||
constraint.constant = -h
|
||||
vc.view.layoutIfNeeded() // hide again
|
||||
}, completion: { _ in
|
||||
self.view.removeFromSuperview()
|
||||
onClose?()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,10 @@ extension Prefs {
|
||||
get { Prefs.Bool("didShowTutorialRecordings") }
|
||||
set { Prefs.Bool("didShowTutorialRecordings", newValue) }
|
||||
}
|
||||
static var RecordingHowTo: Bool {
|
||||
get { Prefs.Bool("didShowTutorialRecordingHowTo") }
|
||||
set { Prefs.Bool("didShowTutorialRecordingHowTo", newValue) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ enum PrefsShared {
|
||||
|
||||
static func registerDefaults() {
|
||||
suite.register(defaults: [
|
||||
"ForceDisconnectSWCD" : true,
|
||||
"RestartReminderEnabled" : true,
|
||||
"RestartReminderWithText" : true,
|
||||
"RestartReminderWithBadge" : true,
|
||||
@@ -26,6 +27,28 @@ enum PrefsShared {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Recording State
|
||||
|
||||
enum CurrentRecordingState : Int {
|
||||
case Off = 0, App = 1, Background = 2
|
||||
}
|
||||
|
||||
extension PrefsShared {
|
||||
static var CurrentlyRecording: CurrentRecordingState {
|
||||
get { CurrentRecordingState(rawValue: Int("CurrentlyRecording")) ?? .Off }
|
||||
set { Int("CurrentlyRecording", newValue.rawValue) }
|
||||
}
|
||||
static var ForceDisconnectUnresolvableDNS: Bool {
|
||||
get { PrefsShared.Bool("ForceDisconnectUnresolvableDNS") }
|
||||
set { PrefsShared.Bool("ForceDisconnectUnresolvableDNS", newValue) }
|
||||
}
|
||||
static var ForceDisconnectSWCD: Bool {
|
||||
get { PrefsShared.Bool("ForceDisconnectSWCD") }
|
||||
set { PrefsShared.Bool("ForceDisconnectSWCD", newValue) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
extension PrefsShared {
|
||||
|
||||
68
main/Common Classes/TinyMarkdown.swift
Normal file
@@ -0,0 +1,68 @@
|
||||
import UIKit
|
||||
|
||||
struct TinyMarkdown {
|
||||
/// Load markdown file and run through a (very) simple parser (see below).
|
||||
/// - Parameters:
|
||||
/// - filename: Will automatically append `.md` extension
|
||||
/// - replacements: Replace a single occurrence of search string with an attributed replacement.
|
||||
static func load(_ filename: String, replacements: [String : NSMutableAttributedString] = [:]) -> UITextView {
|
||||
let url = Bundle.main.url(forResource: filename, withExtension: "md")!
|
||||
let str = NSMutableAttributedString(withMarkdown: try! String(contentsOf: url))
|
||||
for (key, val) in replacements {
|
||||
guard let r = str.string.range(of: key) else {
|
||||
QLog.Debug("WARN: markdown key '\(key)' does not exist in \(filename)")
|
||||
continue
|
||||
}
|
||||
str.replaceCharacters(in: NSRange(r, in: str.string), with: val)
|
||||
}
|
||||
return QuickUI.text(attributed: str)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSMutableAttributedString {
|
||||
/// Supports only: `#h1`, `##h2`, `###h3`, `_italic_`, `__bold__`, `___boldItalic___`
|
||||
convenience init(withMarkdown content: String) {
|
||||
self.init()
|
||||
let emph = try! NSRegularExpression(pattern: #"(?<=(^|\W))(_{1,3})(\S|\S.*?\S)\2"#, options: [])
|
||||
beginEditing()
|
||||
content.enumerateLines { (line, _) in
|
||||
if line.starts(with: "#") {
|
||||
var h = 0
|
||||
for char in line {
|
||||
if char == "#" { h += 1 }
|
||||
else { break }
|
||||
}
|
||||
var line = line
|
||||
line.removeFirst(h)
|
||||
line = line.trimmingCharacters(in: CharacterSet(charactersIn: " "))
|
||||
switch h {
|
||||
case 1: self.h1(line + "\n")
|
||||
case 2: self.h2(line + "\n")
|
||||
default: self.h3(line + "\n")
|
||||
}
|
||||
} else {
|
||||
let nsline = line as NSString
|
||||
let range = NSRange(location: 0, length: nsline.length)
|
||||
var i = 0
|
||||
for x in emph.matches(in: line, options: [], range: range) {
|
||||
let r = x.range
|
||||
self.normal(nsline.substring(from: i, to: r.location))
|
||||
i = r.upperBound
|
||||
let before = nsline.substring(with: r)
|
||||
let after = before.trimmingCharacters(in: CharacterSet(charactersIn: "_"))
|
||||
switch (before.count - after.count) / 2 {
|
||||
case 1: self.italic(after)
|
||||
case 2: self.bold(after)
|
||||
default: self.boldItalic(after)
|
||||
}
|
||||
}
|
||||
if i < range.length {
|
||||
self.normal(nsline.substring(from: i, to: range.length) + "\n")
|
||||
} else {
|
||||
self.normal("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
endEditing()
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
||||
return x
|
||||
}()
|
||||
|
||||
private let button: UIButton = {
|
||||
private lazy var button: UIButton = {
|
||||
let x = QuickUI.button("", target: self, action: #selector(buttonTapped))
|
||||
x.contentEdgeInsets = UIEdgeInsets(all: 8)
|
||||
return x
|
||||
@@ -132,9 +132,9 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
||||
sheetBg.addSubview(button)
|
||||
|
||||
pager.anchor([.top, .left, .right], to: sheetBg)
|
||||
pageScroll.topAnchor =&= pager.bottomAnchor
|
||||
pageScroll.topAnchor =&= pager.bottomAnchor | .defaultHigh
|
||||
pageScroll.anchor([.left, .right, .top, .bottom], to: sheetBg, margin: sheetInset) | .defaultHigh
|
||||
button.topAnchor =&= pageScroll.bottomAnchor
|
||||
button.topAnchor =&= pageScroll.bottomAnchor | .defaultHigh
|
||||
button.anchor([.bottom, .centerX], to: sheetBg)
|
||||
// button.bottomAnchor =&= sheetBg.bottomAnchor - 30
|
||||
// button.centerXAnchor =&= sheetBg.centerXAnchor
|
||||
|
||||
@@ -20,25 +20,19 @@ extension SQLiteDatabase {
|
||||
try ifStep(stmt, SQLITE_ROW)
|
||||
return sqlite3_column_int(stmt, 0)
|
||||
}
|
||||
if version != 1 {
|
||||
if version != 2 {
|
||||
QLog.Info("migrate db \(version) -> 2")
|
||||
// version 0 -> 1: req(domain) -> heap(fqdn, domain)
|
||||
if version == 0 {
|
||||
try tempMigrate()
|
||||
// version 1 -> 2: rec(+subtitle, +opt)
|
||||
if version == 1 {
|
||||
transaction("""
|
||||
ALTER TABLE rec ADD COLUMN subtitle TEXT;
|
||||
ALTER TABLE rec ADD COLUMN uploadkey TEXT;
|
||||
""")
|
||||
}
|
||||
try run(sql: "PRAGMA user_version = 1;")
|
||||
try run(sql: "PRAGMA user_version = 2;")
|
||||
}
|
||||
}
|
||||
|
||||
private func tempMigrate() throws { // TODO: remove with next internal release
|
||||
do {
|
||||
try run(sql: "SELECT 1 FROM req LIMIT 1;") // fails if req doesnt exist
|
||||
createFunction("domainof") { ($0.first as! String).extractDomain() }
|
||||
transaction("""
|
||||
INSERT INTO heap(ts,fqdn,domain,opt) SELECT ts,domain,domainof(domain),nullif(logOpt,0) FROM req;
|
||||
DROP TABLE req;
|
||||
""")
|
||||
} catch { /* no need to migrate */ }
|
||||
}
|
||||
}
|
||||
|
||||
private enum TableName: String {
|
||||
@@ -117,12 +111,11 @@ extension SQLiteDatabase {
|
||||
/// - Returns: `nil` in case no entries were transmitted.
|
||||
@discardableResult func dnsLogsPersist() -> SQLiteRowRange? {
|
||||
guard lastRowId(.cache) > 0 else { return nil }
|
||||
transaction("ALTER TABLE cache RENAME TO tmp_cache; \(CreateTable.cache)")
|
||||
let before = lastRowId(.heap) + 1
|
||||
createFunction("domainof") { ($0.first as! String).extractDomain() }
|
||||
transaction("""
|
||||
INSERT INTO heap(ts,fqdn,domain,opt) SELECT ts,dns,domainof(dns),nullif(opt&1,0) FROM tmp_cache;
|
||||
DROP TABLE tmp_cache;
|
||||
INSERT INTO heap(ts,fqdn,domain,opt) SELECT ts,dns,domainof(dns),nullif(opt&1,0) FROM cache;
|
||||
DELETE FROM cache;
|
||||
""")
|
||||
let after = lastRowId(.heap)
|
||||
return (before > after) ? nil : (before, after)
|
||||
@@ -296,7 +289,7 @@ extension SQLiteDatabase {
|
||||
// MARK: - Recordings
|
||||
|
||||
extension CreateTable {
|
||||
/// `id`: Primary, `start`: Timestamp, `stop`: Timestamp, `appid`: String, `title`: String, `notes`: String
|
||||
/// `id`: Primary, `start`: Timestamp, `stop`: Timestamp, `appid`: String, `title`: String, `subtitle`: String, `notes`: String
|
||||
static var rec: String {"""
|
||||
CREATE TABLE IF NOT EXISTS rec(
|
||||
id INTEGER PRIMARY KEY,
|
||||
@@ -304,19 +297,25 @@ extension CreateTable {
|
||||
stop INTEGER,
|
||||
appid TEXT,
|
||||
title TEXT,
|
||||
notes TEXT
|
||||
subtitle TEXT,
|
||||
notes TEXT,
|
||||
uploadkey TEXT
|
||||
);
|
||||
"""}
|
||||
}
|
||||
|
||||
let readRecordingSelect = "id, start, stop, appid, title, subtitle, notes, uploadkey"
|
||||
struct Recording {
|
||||
let id: sqlite3_int64
|
||||
let start: Timestamp
|
||||
let stop: Timestamp?
|
||||
var appId: String? = nil
|
||||
var title: String? = nil
|
||||
var subtitle: String? = nil
|
||||
var notes: String? = nil
|
||||
var uploadkey: String? = nil
|
||||
}
|
||||
typealias AppBundleInfo = (bundleId: String, name: String?, author: String?)
|
||||
|
||||
extension SQLiteDatabase {
|
||||
|
||||
@@ -343,8 +342,9 @@ extension SQLiteDatabase {
|
||||
|
||||
/// Update given recording by replacing `title`, `appid`, and `notes` with new values.
|
||||
func recordingUpdate(_ r: Recording) {
|
||||
try? run(sql: "UPDATE rec SET title = ?, appid = ?, notes = ? WHERE id = ? LIMIT 1;",
|
||||
bind: [BindTextOrNil(r.title), BindTextOrNil(r.appId), BindTextOrNil(r.notes), BindInt64(r.id)]) { stmt -> Void in
|
||||
try? run(sql: "UPDATE rec SET appid = ?, title = ?, subtitle = ?, notes = ?, uploadkey = ? WHERE id = ? LIMIT 1;",
|
||||
bind: [BindTextOrNil(r.appId), BindTextOrNil(r.title), BindTextOrNil(r.subtitle),
|
||||
BindTextOrNil(r.notes), BindTextOrNil(r.uploadkey), BindInt64(r.id)]) { stmt -> Void in
|
||||
sqlite3_step(stmt)
|
||||
}
|
||||
}
|
||||
@@ -368,12 +368,14 @@ extension SQLiteDatabase {
|
||||
stop: end == 0 ? nil : end,
|
||||
appId: col_text(stmt, 3),
|
||||
title: col_text(stmt, 4),
|
||||
notes: col_text(stmt, 5))
|
||||
subtitle: col_text(stmt, 5),
|
||||
notes: col_text(stmt, 6),
|
||||
uploadkey: col_text(stmt, 7))
|
||||
}
|
||||
|
||||
/// `WHERE stop IS NULL`
|
||||
func recordingGetOngoing() -> Recording? {
|
||||
try? run(sql: "SELECT * FROM rec WHERE stop IS NULL LIMIT 1;") {
|
||||
try? run(sql: "SELECT \(readRecordingSelect) FROM rec WHERE stop IS NULL LIMIT 1;") {
|
||||
try ifStep($0, SQLITE_ROW)
|
||||
return readRecording($0)
|
||||
}
|
||||
@@ -389,18 +391,26 @@ extension SQLiteDatabase {
|
||||
|
||||
/// `WHERE stop IS NOT NULL`
|
||||
func recordingGetAll() -> [Recording]? {
|
||||
try? run(sql: "SELECT * FROM rec WHERE stop IS NOT NULL;") {
|
||||
try? run(sql: "SELECT \(readRecordingSelect) FROM rec WHERE stop IS NOT NULL;") {
|
||||
allRows($0) { readRecording($0) }
|
||||
}
|
||||
}
|
||||
|
||||
/// `WHERE id = ?`
|
||||
private func recordingGet(withID: sqlite3_int64) throws -> Recording {
|
||||
try run(sql: "SELECT * FROM rec WHERE id = ? LIMIT 1;", bind: [BindInt64(withID)]) {
|
||||
try run(sql: "SELECT \(readRecordingSelect) FROM rec WHERE id = ? LIMIT 1;", bind: [BindInt64(withID)]) {
|
||||
try ifStep($0, SQLITE_ROW)
|
||||
return readRecording($0)
|
||||
}
|
||||
}
|
||||
|
||||
func appBundleList() -> [AppBundleInfo]? {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -48,11 +48,16 @@ extension SQLiteDatabase {
|
||||
/// - Returns: `true` if at least one row was deleted.
|
||||
@discardableResult func dnsLogsDeleteOlderThan(days: Int) throws -> Bool {
|
||||
guard days > 0 else { return false }
|
||||
return try self.run(sql: "DELETE FROM cache WHERE ts < strftime('%s', 'now', ?);",
|
||||
bind: [BindText("-\(days) days")]) {
|
||||
try ifStep($0, SQLITE_DONE)
|
||||
return numberOfChanges > 0
|
||||
func delFrom(_ table: String) throws -> Bool {
|
||||
return try self.run(sql: "DELETE FROM \(table) WHERE ts < strftime('%s', 'now', ?);",
|
||||
bind: [BindText("-\(days) days")]) {
|
||||
try ifStep($0, SQLITE_DONE)
|
||||
return numberOfChanges > 0
|
||||
}
|
||||
}
|
||||
let didDelHeap = try delFrom("heap")
|
||||
let didDelCache = try delFrom("cache")
|
||||
return didDelHeap || didDelCache
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ class SQLiteDatabase {
|
||||
static func open(path: String = URL.internalDB().relativePath) throws -> SQLiteDatabase {
|
||||
var db: OpaquePointer?
|
||||
if sqlite3_open_v2(path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nil) == SQLITE_OK {
|
||||
sqlite3_busy_timeout(db, 800)
|
||||
return SQLiteDatabase(dbPointer: db)
|
||||
} else {
|
||||
defer { sqlite3_close_v2(db) }
|
||||
@@ -168,6 +169,10 @@ protocol DBBinding {
|
||||
func bind(_ stmt: OpaquePointer!, _ col: Int32) -> Int32
|
||||
}
|
||||
|
||||
struct BindNull : DBBinding {
|
||||
func bind(_ stmt: OpaquePointer!, _ col: Int32) -> Int32 { sqlite3_bind_null(stmt, col) }
|
||||
}
|
||||
|
||||
struct BindInt32 : DBBinding {
|
||||
let raw: Int32
|
||||
init(_ value: Int32) { raw = value }
|
||||
|
||||
@@ -33,7 +33,13 @@ extension FilterOptions {
|
||||
}
|
||||
|
||||
extension Recording {
|
||||
var fallbackTitle: String { get { "Unnamed Recording #\(id)" } }
|
||||
var duration: Timestamp? { get { stop == nil ? nil : stop! - start } }
|
||||
static let minTimeLongTerm: Timestamp = .hours(1)
|
||||
|
||||
var fallbackTitle: String { get {
|
||||
isLongTerm ? "Background Recording" : "Unnamed Recording #\(id)"
|
||||
} }
|
||||
var duration: Timestamp { get { (stop ?? .now()) - start } }
|
||||
var isLongTerm: Bool { duration > Recording.minTimeLongTerm }
|
||||
var isShared: Bool { uploadkey?.count ?? 0 > 0}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,13 @@ struct TheGreatDestroyer {
|
||||
DispatchQueue.global().async {
|
||||
defer { sync.continue() }
|
||||
QLog.Info("Auto-delete logs")
|
||||
guard let success = try? AppDB?.dnsLogsDeleteOlderThan(days: days), success else {
|
||||
return // nothing changed
|
||||
do {
|
||||
if try AppDB!.dnsLogsDeleteOlderThan(days: days) {
|
||||
sync.needsReloadDB()
|
||||
}
|
||||
} catch {
|
||||
QLog.Warning("Couldn't auto-delete logs, \(error)")
|
||||
}
|
||||
sync.needsReloadDB()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,18 @@ enum RecordingsDB {
|
||||
AppDB?.recordingLogsGet(r) ?? []
|
||||
}
|
||||
|
||||
/// Get dictionary of domains with `ts` in ascending order.
|
||||
static func detailCluster(_ r: Recording) -> [String : [Timestamp]] {
|
||||
var cluster: [String : [Timestamp]] = [:]
|
||||
for (dom, ts) in details(r) {
|
||||
if cluster[dom] == nil {
|
||||
cluster[dom] = []
|
||||
}
|
||||
cluster[dom]!.append(ts - r.start)
|
||||
}
|
||||
return cluster
|
||||
}
|
||||
|
||||
/// Update `title`, `appid`, and `notes` and post `NotifyRecordingChanged` notification.
|
||||
static func update(_ r: Recording) {
|
||||
AppDB?.recordingUpdate(r)
|
||||
@@ -52,5 +64,10 @@ enum RecordingsDB {
|
||||
static func deleteSingle(_ r: Recording, domain: String, ts: Timestamp) -> Bool {
|
||||
(try? AppDB?.recordingLogsDelete(r.id, singleEntry: ts, domain: domain)) ?? false
|
||||
}
|
||||
|
||||
/// Return list of previously used apps found in all recordings.
|
||||
static func appList() -> [AppBundleInfo] {
|
||||
AppDB?.appBundleList() ?? []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,14 @@ class SimulatorVPN {
|
||||
|
||||
@objc static func insertRandom() {
|
||||
//QLog.Debug("Inserting 1 periodic log entry")
|
||||
let domain = "\(arc4random() % 5).count.test.com"
|
||||
let rand = arc4random() % 8
|
||||
let domain: String
|
||||
switch rand {
|
||||
case 6: domain = "tmp.b.test.com"
|
||||
case 7: domain = "tmp.i.test.com"
|
||||
case 8: domain = "tmp.bi.test.com"
|
||||
default: domain = "\(rand).count.test.com"
|
||||
}
|
||||
let kill = hook.processDNSRequest(domain)
|
||||
if kill { QLog.Info("Blocked: \(domain)") }
|
||||
}
|
||||
|
||||
@@ -31,17 +31,19 @@ func ErrorAlert(_ errorDescription: String, buttonText: String = "Dismiss") -> U
|
||||
/// - Parameters:
|
||||
/// - buttonText: Default: `"Continue"`
|
||||
/// - buttonStyle: Default: `.default`
|
||||
func AskAlert(title: String?, text: String?, buttonText: String = "Continue", buttonStyle: UIAlertAction.Style = .default, action: @escaping (UIAlertController) -> Void) -> UIAlertController {
|
||||
let alert = Alert(title: title, text: text, buttonText: "Cancel")
|
||||
func AskAlert(title: String?, text: String?, buttonText: String = "Continue", cancelButton: String = "Cancel", buttonStyle: UIAlertAction.Style = .default, action: @escaping (UIAlertController) -> Void) -> UIAlertController {
|
||||
let alert = Alert(title: title, text: text, buttonText: cancelButton)
|
||||
alert.addAction(UIAlertAction(title: buttonText, style: buttonStyle) { _ in action(alert) })
|
||||
return alert
|
||||
}
|
||||
|
||||
/// Show alert hinting the user to go to system settings and re-enable notifications.
|
||||
func NotificationsDisabledAlert(presentIn viewController: UIViewController) {
|
||||
Alert(title: "Notifications Disabled",
|
||||
text: "Go to System Settings > Notifications > AppCheck to re-enable notifications."
|
||||
).presentIn(viewController)
|
||||
AskAlert(title: "Notifications Disabled",
|
||||
text: "Go to System Settings > Notifications > AppCheck to re-enable notifications.",
|
||||
buttonText: "Open settings") { _ in
|
||||
URL(string: UIApplication.openSettingsURLString)?.open()
|
||||
}.presentIn(viewController)
|
||||
}
|
||||
|
||||
// MARK: Alert with multiple options
|
||||
|
||||
@@ -6,6 +6,7 @@ extension UIFont {
|
||||
}
|
||||
func bold() -> UIFont { withTraits(traits: .traitBold) }
|
||||
func italic() -> UIFont { withTraits(traits: .traitItalic) }
|
||||
func boldItalic() -> UIFont { withTraits(traits: [.traitBold, .traitItalic]) }
|
||||
func monoSpace() -> UIFont {
|
||||
let traits = fontDescriptor.object(forKey: .traits) as? [UIFontDescriptor.TraitKey: Any] ?? [:]
|
||||
let weight = (traits[.weight] as? CGFloat) ?? UIFont.Weight.regular.rawValue
|
||||
@@ -13,24 +14,29 @@ extension UIFont {
|
||||
}
|
||||
}
|
||||
|
||||
extension NSAttributedString {
|
||||
static func image(_ img: UIImage) -> Self {
|
||||
extension NSMutableAttributedString {
|
||||
convenience init(image: UIImage, centered: Bool = false) {
|
||||
self.init()
|
||||
let att = NSTextAttachment()
|
||||
att.image = img
|
||||
return self.init(attachment: att)
|
||||
att.image = image
|
||||
append(.init(attachment: att))
|
||||
if centered {
|
||||
let ps = NSMutableParagraphStyle()
|
||||
ps.alignment = .center
|
||||
addAttribute(.paragraphStyle, value: ps, range: .init(location: 0, length: length))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSMutableAttributedString {
|
||||
static private var def: UIFont = .preferredFont(forTextStyle: .body)
|
||||
@discardableResult func normal(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: .preferredFont(forTextStyle: style)) }
|
||||
@discardableResult func bold(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).bold()) }
|
||||
@discardableResult func italic(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).italic()) }
|
||||
@discardableResult func boldItalic(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).boldItalic()) }
|
||||
|
||||
func normal(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: .preferredFont(forTextStyle: style)) }
|
||||
func bold(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).bold()) }
|
||||
func italic(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).italic()) }
|
||||
|
||||
func h1(_ str: String) -> Self { normal(str, .title1) }
|
||||
func h2(_ str: String) -> Self { normal(str, .title2) }
|
||||
func h3(_ str: String) -> Self { normal(str, .title3) }
|
||||
@discardableResult func h1(_ str: String) -> Self { normal(str, .title1) }
|
||||
@discardableResult func h2(_ str: String) -> Self { normal(str, .title2) }
|
||||
@discardableResult func h3(_ str: String) -> Self { normal(str, .title3) }
|
||||
|
||||
private func append(_ str: String, withFont: UIFont) -> Self {
|
||||
append(NSAttributedString(string: str, attributes: [
|
||||
@@ -39,13 +45,4 @@ extension NSMutableAttributedString {
|
||||
]))
|
||||
return self
|
||||
}
|
||||
|
||||
func centered(_ content: NSAttributedString) -> Self {
|
||||
let before = length
|
||||
append(content)
|
||||
let ps = NSMutableParagraphStyle()
|
||||
ps.alignment = .center
|
||||
addAttribute(.paragraphStyle, value: ps, range: .init(location: before, length: content.length))
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,13 @@ extension String {
|
||||
let parts = components(separatedBy: ".")
|
||||
return parts.count == 2 && listOfSLDs[parts.last!]?[parts.first!] ?? false
|
||||
}
|
||||
|
||||
func isValidBundleId() -> Bool {
|
||||
let regex = try! NSRegularExpression(pattern: #"^[A-Za-z0-9\.\-]{1,155}$"#, options: .anchorsMatchLines)
|
||||
let range = NSRange(location: 0, length: self.utf16.count)
|
||||
let matches = regex.matches(in: self, options: .anchored, range: range)
|
||||
return matches.count == 1
|
||||
}
|
||||
}
|
||||
|
||||
private var listOfSLDs: [String : [String : Bool]] = {
|
||||
@@ -40,3 +47,9 @@ private var listOfSLDs: [String : [String : Bool]] = {
|
||||
}
|
||||
return res
|
||||
}()
|
||||
|
||||
extension NSString {
|
||||
func substring(from: Int, to: Int) -> String {
|
||||
substring(with: NSRange(location: from, length: to - from))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,3 +90,34 @@ extension EditActionsRemove where Self: UITableViewController {
|
||||
func editableRowActions(_: IndexPath) -> [(RowAction, String)] { [(.delete, "Remove")] }
|
||||
func editableRowActionColor(_: IndexPath, _: RowAction) -> UIColor? { nil }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table Cell Tap Menu
|
||||
|
||||
struct TableCellTapMenu {
|
||||
private var index: Int = Int.max
|
||||
|
||||
mutating func reset() { index = Int.max }
|
||||
|
||||
/// Create a new tap manu and shows it immediatelly. With optional buttons.
|
||||
mutating func start(_ tableView: UITableView, _ indexPath: IndexPath, items: [UIMenuItem]? = nil) -> Bool {
|
||||
let menu = UIMenuController.shared
|
||||
if index == indexPath.row {
|
||||
menu.setMenuVisible(false, animated: true)
|
||||
reset()
|
||||
return false
|
||||
}
|
||||
index = indexPath.row
|
||||
let cell = tableView.cellForRow(at: indexPath)!
|
||||
menu.setTargetRect(cell.bounds, in: cell)
|
||||
menu.menuItems = items
|
||||
menu.setMenuVisible(true, animated: true)
|
||||
return true
|
||||
}
|
||||
|
||||
/// Returns the item if the array index is in bounds.
|
||||
func getSelected<T>(_ source: [T]) -> T? {
|
||||
guard index < source.count else { return nil }
|
||||
return source[index]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,17 +86,28 @@ struct TimeFormat {
|
||||
}
|
||||
|
||||
/// Duration string with format `mm:ss` or `mm:ss.SSS`
|
||||
static func from(_ duration: TimeInterval, millis: Bool = false) -> String {
|
||||
let t = Int(duration)
|
||||
static func from(_ duration: TimeInterval, millis: Bool = false, hours: Bool = false) -> String {
|
||||
var t = Int(duration)
|
||||
var min = t / 60
|
||||
var sec = t % 60
|
||||
if millis {
|
||||
let mil = Int(duration * 1000) % 1000
|
||||
return String(format: "%02d:%02d.%03d", t / 60, t % 60, mil)
|
||||
return String(format: "%02d:%02d.%03d", min, sec, mil)
|
||||
} else if hours {
|
||||
if t < Recording.minTimeLongTerm {
|
||||
t = Int(Recording.minTimeLongTerm) - t
|
||||
min = t / 60
|
||||
sec = t % 60
|
||||
return String(format: "-%02d:%02d:%02d", min / 60, min % 60, sec)
|
||||
} else {
|
||||
return String(format: "%02d:%02d:%02d", min / 60, min % 60, sec)
|
||||
}
|
||||
}
|
||||
return String(format: "%02d:%02d", t / 60, t % 60)
|
||||
return String(format: "%02d:%02d", min, sec)
|
||||
}
|
||||
|
||||
/// Duration string with format `mm:ss` or `mm:ss.SSS` since reference date
|
||||
static func since(_ date: Date, millis: Bool = false) -> String {
|
||||
from(Date().timeIntervalSince(date), millis: millis)
|
||||
/// Duration string with format `mm:ss` or `mm:ss.SSS` or `HH:mm:ss` since reference date
|
||||
static func since(_ date: Date, millis: Bool = false, hours: Bool = false) -> String {
|
||||
from(Date().timeIntervalSince(date), millis: millis, hours: hours)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
fileprivate extension FileManager {
|
||||
// func exportDir() -> URL {
|
||||
// try! url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||
// }
|
||||
func documentDir() -> URL {
|
||||
try! url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||
}
|
||||
func appGroupDir() -> URL {
|
||||
containerURL(forSecurityApplicationGroupIdentifier: "group.de.uni-bamberg.psi.AppCheck")!
|
||||
}
|
||||
@@ -25,7 +25,34 @@ extension FileManager {
|
||||
}
|
||||
|
||||
extension URL {
|
||||
// static func exportDir() -> URL { FileManager.default.exportDir() }
|
||||
static func documentDir() -> URL { FileManager.default.documentDir() }
|
||||
static func appGroupDir() -> URL { FileManager.default.appGroupDir() }
|
||||
static func internalDB() -> URL { FileManager.default.internalDB() }
|
||||
|
||||
static func make(_ base: String, params: [String : String]) -> URL? {
|
||||
guard var components = URLComponents(string: base) else {
|
||||
return nil
|
||||
}
|
||||
components.queryItems = params.map {
|
||||
URLQueryItem(name: $0, value: $1)
|
||||
}
|
||||
components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
|
||||
return components.url
|
||||
}
|
||||
|
||||
@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 {
|
||||
try FileManager.default.moveItem(at: loc, to: file)
|
||||
onSuccess()
|
||||
} catch {
|
||||
NSLog("[VPN.ERROR] \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
return task
|
||||
}
|
||||
}
|
||||
|
||||
207
main/GUI/Base.lproj/CoOccurrence.storyboard
Normal file
@@ -0,0 +1,207 @@
|
||||
<?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="W5Q-oz-bFb">
|
||||
<device id="retina4_0" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Co Occurrence-->
|
||||
<scene sceneID="Gbm-AP-b72">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="IBCoOccurrence" id="W5Q-oz-bFb" customClass="VCCoOccurrence" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="f34-NO-d8f">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<navigationBar contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rvt-nC-2Zr">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<items>
|
||||
<navigationItem title="Co-Occurrence" id="csY-x8-Rpe">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="done" id="eg9-p3-Xas">
|
||||
<connections>
|
||||
<action selector="didClose:" destination="W5Q-oz-bFb" id="wyw-vo-6xL"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" image="detail-help" id="RTh-uI-ST6">
|
||||
<connections>
|
||||
<action selector="showInfoScreen" destination="W5Q-oz-bFb" id="xoS-z7-PHr"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</items>
|
||||
</navigationBar>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="PGb-pB-cfO">
|
||||
<rect key="frame" x="0.0" y="44" width="320" height="524"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<segmentedControl key="tableHeaderView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" id="7ye-tU-pdo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<segments>
|
||||
<segment title="10s"/>
|
||||
<segment title="30s"/>
|
||||
</segments>
|
||||
<connections>
|
||||
<action selector="didChangeTime:" destination="W5Q-oz-bFb" eventType="valueChanged" id="c5h-JG-S19"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="CoOccurrenceCell" rowHeight="72" id="2qH-Bh-644" customClass="CoOccurrenceCell" customModule="AppCheck" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="60" width="320" height="72"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="2qH-Bh-644" id="Lwk-Uj-viQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="72"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="99." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qaw-ql-zIB" customClass="TagLabel" customModule="AppCheck" customModuleProvider="target">
|
||||
<rect key="frame" x="15" y="39.5" width="32" height="21.5"/>
|
||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="qaw-ql-zIB" secondAttribute="height" multiplier="3:2" id="VOJ-f5-xhk"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" translatesAutoresizingMaskIntoConstraints="NO" id="zbU-wC-qJG">
|
||||
<rect key="frame" x="15" y="11" width="290" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" horizontalCompressionResistancePriority="500" insetsLayoutMarginsFromSafeArea="NO" text="Count" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JWp-6l-HTJ">
|
||||
<rect key="frame" x="109.5" y="42.5" width="37" height="16"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="5900" textAlignment="natural" lineBreakMode="clip" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="q5v-FM-iGo" customClass="TagLabel" customModule="AppCheck" customModuleProvider="target">
|
||||
<rect key="frame" x="150.5" y="39.5" width="42.5" height="21.5"/>
|
||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="padRight">
|
||||
<real key="value" value="1"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="10.35s" textAlignment="natural" lineBreakMode="clip" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zCg-I0-4Tz" customClass="TagLabel" customModule="AppCheck" customModuleProvider="target">
|
||||
<rect key="frame" x="255.5" y="39.5" width="49.5" height="21.5"/>
|
||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="padRight">
|
||||
<real key="value" value="1"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" horizontalHuggingPriority="750" horizontalCompressionResistancePriority="400" insetsLayoutMarginsFromSafeArea="NO" text="Diverge" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="T4X-cn-msT">
|
||||
<rect key="frame" x="205" y="42.5" width="46.5" height="16"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9Bb-e5-D3O" customClass="MeterBar" customModule="AppCheck" customModuleProvider="target">
|
||||
<rect key="frame" x="190" y="39.5" width="3" height="21.5"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="3" id="wWb-VG-Kqa"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="percent">
|
||||
<real key="value" value="1"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="color" keyPath="barColor">
|
||||
<color key="value" systemColor="systemOrangeColor" red="1" green="0.58431372550000005" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JwY-mq-rYZ" customClass="MeterBar" customModule="AppCheck" customModuleProvider="target">
|
||||
<rect key="frame" x="302" y="39.5" width="3" height="21.5"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="3" id="Tta-m5-vwa"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="percent">
|
||||
<real key="value" value="1"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="color" keyPath="barColor">
|
||||
<color key="value" systemColor="systemOrangeColor" red="1" green="0.58431372550000005" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="qaw-ql-zIB" firstAttribute="height" secondItem="q5v-FM-iGo" secondAttribute="height" id="2Ug-qN-ido"/>
|
||||
<constraint firstItem="zbU-wC-qJG" firstAttribute="leading" secondItem="Lwk-Uj-viQ" secondAttribute="leadingMargin" id="2Zo-jC-08y"/>
|
||||
<constraint firstItem="JwY-mq-rYZ" firstAttribute="top" secondItem="zCg-I0-4Tz" secondAttribute="top" id="3MU-gk-eUU"/>
|
||||
<constraint firstItem="T4X-cn-msT" firstAttribute="height" secondItem="zCg-I0-4Tz" secondAttribute="height" multiplier="0.75" id="AwE-JC-MFF"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="q5v-FM-iGo" secondAttribute="bottom" id="B2M-MQ-kAw"/>
|
||||
<constraint firstItem="9Bb-e5-D3O" firstAttribute="bottom" secondItem="q5v-FM-iGo" secondAttribute="bottom" id="Efb-Ud-lxb"/>
|
||||
<constraint firstItem="JWp-6l-HTJ" firstAttribute="height" secondItem="q5v-FM-iGo" secondAttribute="height" multiplier="0.75" id="Gfb-up-g1b"/>
|
||||
<constraint firstItem="JwY-mq-rYZ" firstAttribute="trailing" secondItem="zCg-I0-4Tz" secondAttribute="trailing" id="RlS-DQ-pdh"/>
|
||||
<constraint firstItem="zCg-I0-4Tz" firstAttribute="leading" secondItem="T4X-cn-msT" secondAttribute="trailing" constant="4" id="VpT-5w-aKh"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="zCg-I0-4Tz" secondAttribute="trailing" id="ai7-PW-ISq"/>
|
||||
<constraint firstItem="qaw-ql-zIB" firstAttribute="leading" secondItem="Lwk-Uj-viQ" secondAttribute="leadingMargin" id="bGT-hc-lSG"/>
|
||||
<constraint firstItem="JWp-6l-HTJ" firstAttribute="height" secondItem="T4X-cn-msT" secondAttribute="height" id="cKO-4d-ikl"/>
|
||||
<constraint firstItem="JWp-6l-HTJ" firstAttribute="centerY" secondItem="q5v-FM-iGo" secondAttribute="centerY" id="dZr-0G-1sp"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="zbU-wC-qJG" secondAttribute="trailing" id="e7x-RS-YWo"/>
|
||||
<constraint firstItem="JwY-mq-rYZ" firstAttribute="bottom" secondItem="zCg-I0-4Tz" secondAttribute="bottom" id="fAV-yh-H1r"/>
|
||||
<constraint firstItem="9Bb-e5-D3O" firstAttribute="trailing" secondItem="q5v-FM-iGo" secondAttribute="trailing" id="fFF-y3-qOe"/>
|
||||
<constraint firstItem="qaw-ql-zIB" firstAttribute="top" secondItem="zbU-wC-qJG" secondAttribute="bottom" constant="8" symbolic="YES" id="fgw-q8-YRD"/>
|
||||
<constraint firstItem="9Bb-e5-D3O" firstAttribute="top" secondItem="q5v-FM-iGo" secondAttribute="top" id="idg-nm-vIj"/>
|
||||
<constraint firstItem="T4X-cn-msT" firstAttribute="leading" secondItem="q5v-FM-iGo" secondAttribute="trailing" constant="12" id="kZj-Tn-BQ3"/>
|
||||
<constraint firstItem="zbU-wC-qJG" firstAttribute="top" secondItem="Lwk-Uj-viQ" secondAttribute="top" constant="11" id="o7o-M0-sA2"/>
|
||||
<constraint firstItem="q5v-FM-iGo" firstAttribute="top" secondItem="zbU-wC-qJG" secondAttribute="bottom" constant="8" symbolic="YES" id="peW-Pg-5WC"/>
|
||||
<constraint firstItem="zCg-I0-4Tz" firstAttribute="top" secondItem="zbU-wC-qJG" secondAttribute="bottom" constant="8" symbolic="YES" id="ttp-yA-tsi"/>
|
||||
<constraint firstItem="T4X-cn-msT" firstAttribute="centerY" secondItem="zCg-I0-4Tz" secondAttribute="centerY" id="tz9-Vr-fB6"/>
|
||||
<constraint firstItem="JWp-6l-HTJ" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="qaw-ql-zIB" secondAttribute="trailing" constant="8" symbolic="YES" id="xFl-RU-Ynw"/>
|
||||
<constraint firstItem="q5v-FM-iGo" firstAttribute="leading" secondItem="JWp-6l-HTJ" secondAttribute="trailing" constant="4" id="xHw-Pf-daH"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="avgdiff" destination="zCg-I0-4Tz" id="Jno-Yc-ngL"/>
|
||||
<outlet property="avgdiffMeter" destination="JwY-mq-rYZ" id="QNx-rP-17Z"/>
|
||||
<outlet property="count" destination="q5v-FM-iGo" id="AFk-93-mhs"/>
|
||||
<outlet property="countMeter" destination="9Bb-e5-D3O" id="zqt-dT-ecT"/>
|
||||
<outlet property="rank" destination="qaw-ql-zIB" id="q6Y-JS-NFU"/>
|
||||
<outlet property="title" destination="zbU-wC-qJG" id="hgV-L0-blX"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="W5Q-oz-bFb" id="7lD-aQ-QhQ"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="PGb-pB-cfO" firstAttribute="top" secondItem="rvt-nC-2Zr" secondAttribute="bottom" id="Edp-lx-Xld"/>
|
||||
<constraint firstItem="4eZ-5P-8sz" firstAttribute="bottom" secondItem="PGb-pB-cfO" secondAttribute="bottom" id="OAG-HL-4N4"/>
|
||||
<constraint firstItem="PGb-pB-cfO" firstAttribute="leading" secondItem="4eZ-5P-8sz" secondAttribute="leading" id="V6d-HM-JzJ"/>
|
||||
<constraint firstItem="4eZ-5P-8sz" firstAttribute="trailing" secondItem="rvt-nC-2Zr" secondAttribute="trailing" id="cmE-iH-06W"/>
|
||||
<constraint firstItem="rvt-nC-2Zr" firstAttribute="top" secondItem="4eZ-5P-8sz" secondAttribute="top" id="epT-LW-CJV"/>
|
||||
<constraint firstItem="4eZ-5P-8sz" firstAttribute="trailing" secondItem="PGb-pB-cfO" secondAttribute="trailing" id="j8i-8q-qGS"/>
|
||||
<constraint firstItem="rvt-nC-2Zr" firstAttribute="leading" secondItem="4eZ-5P-8sz" secondAttribute="leading" id="skN-SN-Wu7"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="4eZ-5P-8sz"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="tableView" destination="PGb-pB-cfO" id="5gT-KC-ce5"/>
|
||||
<outlet property="timeSegment" destination="7ye-tU-pdo" id="2ys-X4-Jff"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="yYY-5U-gct" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="0.0" y="0.0"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="detail-help" width="22" height="22"/>
|
||||
</resources>
|
||||
</document>
|
||||
116
main/GUI/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,116 @@
|
||||
<?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="sfA-EG-18J">
|
||||
<device id="retina4_0" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Main-->
|
||||
<scene sceneID="7Rl-BK-ry5">
|
||||
<objects>
|
||||
<tabBarController id="sfA-EG-18J" customClass="TBCMain" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="qza-ey-Iaz">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tabBar>
|
||||
<connections>
|
||||
<segue destination="RcB-4v-fd4" kind="relationship" relationship="viewControllers" id="cmC-pu-5n2"/>
|
||||
<segue destination="xaF-Q9-CPX" kind="relationship" relationship="viewControllers" id="bi1-vo-w1d"/>
|
||||
<segue destination="dIk-JY-9vE" kind="relationship" relationship="viewControllers" id="ude-bF-ump"/>
|
||||
</connections>
|
||||
</tabBarController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="RDz-8t-yhN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="0.0" y="-150"/>
|
||||
</scene>
|
||||
<!--Requests-->
|
||||
<scene sceneID="bDO-X1-bCe">
|
||||
<objects>
|
||||
<navigationController id="RcB-4v-fd4" sceneMemberID="viewController">
|
||||
<tabBarItem key="tabBarItem" title="Requests" image="journal" id="Sj5-Kb-Li8"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="HWd-73-m8j">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="H0N-TG-Ck1" kind="relationship" relationship="rootViewController" id="jDh-HG-7ke"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="8j4-AX-JBN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-700" y="650"/>
|
||||
</scene>
|
||||
<!--Requests-->
|
||||
<scene sceneID="kwb-zk-i3S">
|
||||
<objects>
|
||||
<viewControllerPlaceholder storyboardName="Requests" id="H0N-TG-Ck1" sceneMemberID="viewController">
|
||||
<navigationItem key="navigationItem" id="cLA-5B-TZx"/>
|
||||
</viewControllerPlaceholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="rwq-DI-GUl" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-700" y="1150"/>
|
||||
</scene>
|
||||
<!--Recordings-->
|
||||
<scene sceneID="V1I-GW-1gY">
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="df6-nz-nUq" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
<navigationController id="xaF-Q9-CPX" sceneMemberID="viewController">
|
||||
<tabBarItem key="tabBarItem" title="Recordings" image="tag" id="5ww-wt-MOH"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="ZFR-QK-Ofl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="thF-Z0-CBq" kind="relationship" relationship="rootViewController" id="BuU-Uy-Zhv"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="0.0" y="650"/>
|
||||
</scene>
|
||||
<!--Recordings-->
|
||||
<scene sceneID="oLG-Zp-Qm7">
|
||||
<objects>
|
||||
<viewControllerPlaceholder storyboardName="Recordings" id="thF-Z0-CBq" sceneMemberID="viewController">
|
||||
<navigationItem key="navigationItem" id="kMV-TC-eKI"/>
|
||||
</viewControllerPlaceholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="B2d-DP-1Yp" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="0.0" y="1150"/>
|
||||
</scene>
|
||||
<!--Settings-->
|
||||
<scene sceneID="OEQ-fb-haL">
|
||||
<objects>
|
||||
<navigationController id="dIk-JY-9vE" sceneMemberID="viewController">
|
||||
<tabBarItem key="tabBarItem" title="Settings" image="settings" id="dQu-wE-a8u"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="yYW-rX-VnB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="cV2-If-0fV" kind="relationship" relationship="rootViewController" id="vQq-KE-MOO"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bg9-bR-vlx" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="700" y="650"/>
|
||||
</scene>
|
||||
<!--Settings-->
|
||||
<scene sceneID="l0f-fL-3tG">
|
||||
<objects>
|
||||
<viewControllerPlaceholder storyboardName="Settings" id="cV2-If-0fV" sceneMemberID="viewController">
|
||||
<navigationItem key="navigationItem" id="wlO-Ea-6he"/>
|
||||
</viewControllerPlaceholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="i3W-Ff-rJL" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="700" y="1150"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="journal" width="25" height="25"/>
|
||||
<image name="settings" width="25" height="25"/>
|
||||
<image name="tag" width="25" height="25"/>
|
||||
</resources>
|
||||
</document>
|
||||
744
main/GUI/Base.lproj/Recordings.storyboard
Normal file
@@ -0,0 +1,744 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Recordings-->
|
||||
<scene sceneID="ODR-PD-nTU">
|
||||
<objects>
|
||||
<viewController id="hm5-7q-Zfi" customClass="VCRecordings" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="JYr-yE-eGS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="519"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Wz5-zb-gwz">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="519"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ppJ-js-Wwz">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="40"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Start new recording" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sYd-b2-Puz">
|
||||
<rect key="frame" x="20" y="8" width="280" height="24"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="By4-Qr-Zg2">
|
||||
<rect key="frame" x="278" y="9" width="22" height="22"/>
|
||||
<state key="normal" image="detail-help"/>
|
||||
<connections>
|
||||
<action selector="showInfo:" destination="hm5-7q-Zfi" eventType="touchUpInside" id="AiT-rb-53G"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="sYd-b2-Puz" firstAttribute="leading" secondItem="ppJ-js-Wwz" secondAttribute="leading" constant="20" symbolic="YES" id="35n-12-YFH"/>
|
||||
<constraint firstAttribute="bottom" secondItem="sYd-b2-Puz" secondAttribute="bottom" priority="999" constant="8" id="4We-J8-dH8"/>
|
||||
<constraint firstAttribute="trailing" secondItem="sYd-b2-Puz" secondAttribute="trailing" constant="20" symbolic="YES" id="7eW-5L-F4Z"/>
|
||||
<constraint firstItem="sYd-b2-Puz" firstAttribute="top" secondItem="ppJ-js-Wwz" secondAttribute="top" priority="999" constant="8" id="S3K-8P-agX"/>
|
||||
<constraint firstItem="By4-Qr-Zg2" firstAttribute="centerY" secondItem="sYd-b2-Puz" secondAttribute="centerY" id="ScC-FQ-oU3"/>
|
||||
<constraint firstAttribute="trailing" secondItem="By4-Qr-Zg2" secondAttribute="trailing" constant="20" symbolic="YES" id="yLs-7S-Uwz"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="La3-9e-6TK">
|
||||
<rect key="frame" x="0.0" y="40" width="320" height="55"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" momentary="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2MI-6l-YQt">
|
||||
<rect key="frame" x="20" y="8" width="280" height="32"/>
|
||||
<segments>
|
||||
<segment title="App"/>
|
||||
<segment title="Background"/>
|
||||
</segments>
|
||||
<connections>
|
||||
<action selector="startRecording:" destination="hm5-7q-Zfi" eventType="valueChanged" id="4p5-9Q-clW"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="2MI-6l-YQt" firstAttribute="top" secondItem="La3-9e-6TK" secondAttribute="top" priority="999" constant="8" id="TTg-gG-wEP"/>
|
||||
<constraint firstAttribute="bottom" secondItem="2MI-6l-YQt" secondAttribute="bottom" priority="999" constant="16" id="WPY-VT-xfo"/>
|
||||
<constraint firstAttribute="trailing" secondItem="2MI-6l-YQt" secondAttribute="trailing" constant="20" id="hhi-TT-2VS"/>
|
||||
<constraint firstItem="2MI-6l-YQt" firstAttribute="leading" secondItem="La3-9e-6TK" secondAttribute="leading" constant="20" id="p5F-L6-whI"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9Yj-FX-eFd">
|
||||
<rect key="frame" x="0.0" y="95" width="320" height="54.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00.000" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rbR-np-cXD">
|
||||
<rect key="frame" x="8" y="8" width="200" height="38.5"/>
|
||||
<accessibility key="accessibilityConfiguration">
|
||||
<accessibilityTraits key="traits" staticText="YES" updatesFrequently="YES"/>
|
||||
</accessibility>
|
||||
<fontDescription key="fontDescription" type="system" weight="ultraLight" pointSize="32"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vAq-EZ-Gmx">
|
||||
<rect key="frame" x="212" y="8" width="100" height="38.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<state key="normal" title="Stop">
|
||||
<color key="titleColor" systemColor="systemRedColor" red="1" green="0.23137254900000001" blue="0.18823529410000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="stopRecording:" destination="hm5-7q-Zfi" eventType="touchUpInside" id="CWK-Tg-Tb8"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="rbR-np-cXD" firstAttribute="leading" secondItem="9Yj-FX-eFd" secondAttribute="leading" constant="8" id="610-aO-U6Y"/>
|
||||
<constraint firstItem="vAq-EZ-Gmx" firstAttribute="top" secondItem="9Yj-FX-eFd" secondAttribute="top" priority="999" constant="8" id="CKZ-8m-kmt"/>
|
||||
<constraint firstItem="vAq-EZ-Gmx" firstAttribute="leading" secondItem="rbR-np-cXD" secondAttribute="trailing" constant="4" id="Cya-vS-90h"/>
|
||||
<constraint firstAttribute="trailing" secondItem="vAq-EZ-Gmx" secondAttribute="trailing" constant="8" id="DhM-H2-4I7"/>
|
||||
<constraint firstItem="rbR-np-cXD" firstAttribute="width" secondItem="vAq-EZ-Gmx" secondAttribute="width" multiplier="2" id="REp-Ug-zuV"/>
|
||||
<constraint firstAttribute="bottom" secondItem="rbR-np-cXD" secondAttribute="bottom" priority="999" constant="8" id="Ukk-3a-Vsi"/>
|
||||
<constraint firstAttribute="bottom" secondItem="vAq-EZ-Gmx" secondAttribute="bottom" priority="999" constant="8" id="dV2-g0-7gH"/>
|
||||
<constraint firstItem="rbR-np-cXD" firstAttribute="top" secondItem="9Yj-FX-eFd" secondAttribute="top" priority="999" constant="8" id="mrE-Ej-dqM"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="v3Z-HR-abM">
|
||||
<rect key="frame" x="0.0" y="149.5" width="320" height="369.5"/>
|
||||
<connections>
|
||||
<segue destination="Fln-DD-aId" kind="embed" id="eFH-Rl-aBL"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Wz5-zb-gwz" firstAttribute="leading" secondItem="lFq-fl-zah" secondAttribute="leading" id="Sjv-qq-h50"/>
|
||||
<constraint firstItem="Wz5-zb-gwz" firstAttribute="trailing" secondItem="lFq-fl-zah" secondAttribute="trailing" id="hhA-jU-DJS"/>
|
||||
<constraint firstItem="Wz5-zb-gwz" firstAttribute="bottom" secondItem="lFq-fl-zah" secondAttribute="bottom" id="m6I-NP-LhY"/>
|
||||
<constraint firstItem="Wz5-zb-gwz" firstAttribute="top" secondItem="lFq-fl-zah" secondAttribute="top" id="pEc-Cz-v9f"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="lFq-fl-zah"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="ceR-rC-rur"/>
|
||||
<nil key="simulatedTopBarMetrics"/>
|
||||
<simulatedTabBarMetrics key="simulatedBottomBarMetrics" translucent="NO"/>
|
||||
<connections>
|
||||
<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>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Wfy-Tp-A9o" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="0.0" y="0.0"/>
|
||||
</scene>
|
||||
<!--Previous Recordings-->
|
||||
<scene sceneID="RqA-Jc-FDE">
|
||||
<objects>
|
||||
<tableViewController id="Fln-DD-aId" customClass="TVCPreviousRecords" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="7cH-g6-H5z">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="369.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="detailButton" indentationWidth="10" reuseIdentifier="PreviousRecordCell" textLabel="hr0-Xt-5gV" detailTextLabel="Xav-Ub-clj" style="IBUITableViewCellStyleSubtitle" id="3kW-3B-1bx">
|
||||
<rect key="frame" x="0.0" y="28" width="320" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="3kW-3B-1bx" id="OKV-a6-jjd">
|
||||
<rect key="frame" x="0.0" y="0.0" width="280" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" id="hr0-Xt-5gV">
|
||||
<rect key="frame" x="16" y="9" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Xav-Ub-clj">
|
||||
<rect key="frame" x="16" y="32.5" width="44" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="50g-BI-Q6S" kind="push" identifier="openRecordDetailsSegue" id="arP-jR-O9d"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="Fln-DD-aId" id="oHb-mU-M1Z"/>
|
||||
<outlet property="delegate" destination="Fln-DD-aId" id="6PY-c0-Nfp"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Previous Recordings" id="ow1-cy-qXt"/>
|
||||
<connections>
|
||||
<segue destination="VRk-wv-rhk" kind="modal" identifier="editRecordSegue" id="8rY-sA-Iig"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Lta-uo-x4m" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="700" y="0.0"/>
|
||||
</scene>
|
||||
<!--Logs-->
|
||||
<scene sceneID="DxJ-8o-gTM">
|
||||
<objects>
|
||||
<tableViewController id="50g-BI-Q6S" customClass="TVCRecordingDetails" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="cLV-Db-JxM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="369.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="RecordDetailCountedCell" textLabel="rN0-kA-Eln" detailTextLabel="xRp-XG-oKf" style="IBUITableViewCellStyleValue1" id="ceT-cF-lLF">
|
||||
<rect key="frame" x="0.0" y="28" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ceT-cF-lLF" id="c5Y-xg-hSL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" id="rN0-kA-Eln">
|
||||
<rect key="frame" x="16" y="12" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="xRp-XG-oKf">
|
||||
<rect key="frame" x="260" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="RecordDetailShortCell" textLabel="rIc-r4-6pg" detailTextLabel="0pW-ZC-wmh" style="IBUITableViewCellStyleValue2" id="hzU-cx-nIs">
|
||||
<rect key="frame" x="0.0" y="71.5" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="hzU-cx-nIs" id="scX-pQ-E7z">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="right" lineBreakMode="headTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" id="rIc-r4-6pg">
|
||||
<rect key="frame" x="16" y="12" width="91" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" id="0pW-ZC-wmh">
|
||||
<rect key="frame" x="113" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="RecordDetailLongCell" textLabel="xDy-8J-JFT" detailTextLabel="kgF-BN-FdV" style="IBUITableViewCellStyleSubtitle" id="Q4T-JJ-fqY">
|
||||
<rect key="frame" x="0.0" y="115" width="320" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Q4T-JJ-fqY" id="8hy-Rg-b6Q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" id="xDy-8J-JFT">
|
||||
<rect key="frame" x="16" y="9" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" adjustsFontSizeToFit="NO" id="kgF-BN-FdV">
|
||||
<rect key="frame" x="16" y="32.5" width="44" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="RecordNoResultsCell" textLabel="bmQ-Cn-BOm" style="IBUITableViewCellStyleDefault" id="JZ4-vZ-MnG">
|
||||
<rect key="frame" x="0.0" y="172.5" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="JZ4-vZ-MnG" id="TWb-p9-EMM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="– no results –" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000001192092896" adjustsFontSizeToFit="NO" id="bmQ-Cn-BOm">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="50g-BI-Q6S" id="SFM-IM-FRx"/>
|
||||
<outlet property="delegate" destination="50g-BI-Q6S" id="LBY-sp-dg0"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Logs" id="AXT-fV-keV">
|
||||
<rightBarButtonItems>
|
||||
<barButtonItem systemItem="action" id="UkE-Wi-JjW">
|
||||
<connections>
|
||||
<segue destination="1e2-YP-lHV" kind="push" id="JL4-tN-vZg"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem image="line-expand" id="xLc-O7-KVB">
|
||||
<connections>
|
||||
<action selector="toggleDisplayStyle:" destination="50g-BI-Q6S" id="3wo-9O-7gV"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</rightBarButtonItems>
|
||||
</navigationItem>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="lan-I9-b0a" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1400" y="0.0"/>
|
||||
</scene>
|
||||
<!--Contribute-->
|
||||
<scene sceneID="np1-8y-nci">
|
||||
<objects>
|
||||
<tableViewController id="1e2-YP-lHV" customClass="TVCShareRecording" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="Q9f-Bw-9h3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="369.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="shareTextCell" textLabel="Jpk-wK-OOM" style="IBUITableViewCellStyleDefault" id="9z8-9E-JVQ">
|
||||
<rect key="frame" x="0.0" y="55.5" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="9z8-9E-JVQ" id="TtO-r6-0fS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Description" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.90000000000000002" id="Jpk-wK-OOM">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="shareCheckboxCell" textLabel="OM7-b6-6E8" style="IBUITableViewCellStyleDefault" id="q9q-X3-TNl">
|
||||
<rect key="frame" x="0.0" y="99" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="q9q-X3-TNl" id="Hf9-5j-89Q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="OM7-b6-6E8">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5am-ax-7O0">
|
||||
<rect key="frame" x="257" y="6" width="49" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="didChangeNotesCheckbox:" destination="1e2-YP-lHV" eventType="valueChanged" id="xzg-3J-0IT"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="accessoryView" destination="5am-ax-7O0" id="fw9-Nb-leO"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="shareOpenTextCell" textLabel="842-tZ-Dai" style="IBUITableViewCellStyleDefault" id="Fiz-tT-R1A">
|
||||
<rect key="frame" x="0.0" y="142.5" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Fiz-tT-R1A" id="ZfU-g2-ohM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Open Text" lineBreakMode="tailTruncation" numberOfLines="6" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="842-tZ-Dai">
|
||||
<rect key="frame" x="16" y="0.0" width="269" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="5DP-MB-rOM" kind="push" id="sLA-GQ-Jxx"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="shareKeyValueCell" textLabel="skk-x9-pZl" detailTextLabel="Z4C-mX-qNQ" style="IBUITableViewCellStyleValue2" id="3xN-XZ-3WD">
|
||||
<rect key="frame" x="0.0" y="186" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="3xN-XZ-3WD" id="V5u-dB-PAq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="right" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="skk-x9-pZl">
|
||||
<rect key="frame" x="16" y="13" width="91" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Z4C-mX-qNQ">
|
||||
<rect key="frame" x="113" y="13" width="39.5" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="checkmark" indentationWidth="10" reuseIdentifier="shareLogCell" textLabel="c0B-OV-ujb" detailTextLabel="sAD-Ns-DV6" style="IBUITableViewCellStyleSubtitle" id="ilN-ct-Db4">
|
||||
<rect key="frame" x="0.0" y="229.5" width="320" height="58.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ilN-ct-Db4" id="xMK-8l-diB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="280" height="58.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="c0B-OV-ujb">
|
||||
<rect key="frame" x="16" y="9" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="4" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" adjustsFontSizeToFit="NO" id="sAD-Ns-DV6">
|
||||
<rect key="frame" x="16" y="32.5" width="33" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="1e2-YP-lHV" id="nSk-Aa-sk1"/>
|
||||
<outlet property="delegate" destination="1e2-YP-lHV" id="bQg-k9-Jvt"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Contribute" id="rd1-ra-OBF">
|
||||
<barButtonItem key="rightBarButtonItem" title="Send" id="N7P-2k-lkO">
|
||||
<connections>
|
||||
<action selector="shareRecording:" destination="1e2-YP-lHV" id="QrV-1X-bQ3"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="sendButton" destination="N7P-2k-lkO" id="POi-w1-U0C"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="cIS-61-X0s" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2100" y="0.0"/>
|
||||
</scene>
|
||||
<!--Edit Notes-->
|
||||
<scene sceneID="1PP-Uc-VkW">
|
||||
<objects>
|
||||
<viewController hidesBottomBarWhenPushed="YES" id="5DP-MB-rOM" customClass="VCEditText" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F5N-5G-rm9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="325.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" contentInsetAdjustmentBehavior="never" text="Lorem ipsum dolor sit er elit lamet" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="pCU-n1-q6J">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="369.5"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="pCU-n1-q6J" firstAttribute="leading" secondItem="5JR-8n-qUg" secondAttribute="leading" id="7fM-0S-SUi"/>
|
||||
<constraint firstItem="5JR-8n-qUg" firstAttribute="bottom" secondItem="pCU-n1-q6J" secondAttribute="bottom" id="AG3-zo-ek0"/>
|
||||
<constraint firstItem="pCU-n1-q6J" firstAttribute="top" secondItem="5JR-8n-qUg" secondAttribute="top" id="IM4-ty-wxG"/>
|
||||
<constraint firstItem="pCU-n1-q6J" firstAttribute="trailing" secondItem="5JR-8n-qUg" secondAttribute="trailing" id="dMB-pY-HEO"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="5JR-8n-qUg"/>
|
||||
</view>
|
||||
<extendedEdge key="edgesForExtendedLayout"/>
|
||||
<navigationItem key="navigationItem" title="Edit Notes" id="Ex8-YV-Ebv"/>
|
||||
<connections>
|
||||
<outlet property="textBottom" destination="AG3-zo-ek0" id="EXG-Zb-UGb"/>
|
||||
<outlet property="textView" destination="pCU-n1-q6J" id="K4D-t0-F9P"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="l5b-JD-MQb" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2800" y="0.0"/>
|
||||
</scene>
|
||||
<!--Edit Recording-->
|
||||
<scene sceneID="pqx-CU-4AP">
|
||||
<objects>
|
||||
<viewController id="VRk-wv-rhk" customClass="VCEditRecording" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="rXz-Mk-wrK">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="369.5"/>
|
||||
<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="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="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="fPE-i2-I06"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</items>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="B0n-l6-MKc" appends="YES" id="57w-bJ-Vjh"/>
|
||||
</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="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"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="AppCheck" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Et0-8d-CId">
|
||||
<rect key="frame" x="0.0" y="2" width="80" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="University Bamberg" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="l8O-Kw-uc8">
|
||||
<rect key="frame" x="0.0" y="23.5" width="111" height="14.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LaunchIcon.png" translatesAutoresizingMaskIntoConstraints="NO" id="rbW-pK-Kct">
|
||||
<rect key="frame" x="248" y="0.0" width="40" height="40"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="rbW-pK-Kct" secondAttribute="height" multiplier="1:1" id="dV9-kR-y39"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" priority="999" constant="40" id="5ew-Cq-VKh"/>
|
||||
<constraint firstItem="rbW-pK-Kct" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Et0-8d-CId" secondAttribute="trailing" constant="4" id="EzM-br-BIF"/>
|
||||
<constraint firstItem="Et0-8d-CId" firstAttribute="leading" secondItem="Guy-Ra-fpS" secondAttribute="leading" id="F1b-aQ-6rA"/>
|
||||
<constraint firstAttribute="bottom" secondItem="l8O-Kw-uc8" secondAttribute="bottom" priority="999" constant="2" id="Tpw-nU-HHb"/>
|
||||
<constraint firstItem="l8O-Kw-uc8" firstAttribute="top" secondItem="Et0-8d-CId" secondAttribute="bottom" priority="999" constant="1" id="Wpc-8H-6b8"/>
|
||||
<constraint firstItem="l8O-Kw-uc8" firstAttribute="leading" secondItem="Guy-Ra-fpS" secondAttribute="leading" id="Xmq-Pl-TrJ"/>
|
||||
<constraint firstItem="rbW-pK-Kct" firstAttribute="centerY" secondItem="Guy-Ra-fpS" secondAttribute="centerY" id="bYB-Jd-Meb"/>
|
||||
<constraint firstAttribute="trailing" secondItem="rbW-pK-Kct" secondAttribute="trailing" id="bsC-L7-fZn"/>
|
||||
<constraint firstItem="rbW-pK-Kct" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="l8O-Kw-uc8" secondAttribute="trailing" constant="4" id="fJ8-TH-hwT"/>
|
||||
<constraint firstItem="Et0-8d-CId" firstAttribute="top" secondItem="Guy-Ra-fpS" secondAttribute="top" priority="999" constant="2" id="nXu-FP-JVX"/>
|
||||
<constraint firstItem="rbW-pK-Kct" firstAttribute="height" secondItem="Guy-Ra-fpS" secondAttribute="height" id="zLK-Gu-HcF"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="Jab-q2-U9X" appends="YES" id="V27-A7-AL5"/>
|
||||
</connections>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ybL-UG-dwT" userLabel="Notes">
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<nil key="textColor"/>
|
||||
<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="136.5"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<string key="text">1. Line
|
||||
2. Line
|
||||
3. Line
|
||||
4. Line</string>
|
||||
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="VRk-wv-rhk" id="vej-jI-13V"/>
|
||||
</connections>
|
||||
</textView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="NXU-yU-eST" firstAttribute="leading" secondItem="ybL-UG-dwT" secondAttribute="leading" id="D6U-8L-f9m"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="107" id="Pfy-uW-kRl"/>
|
||||
<constraint firstItem="NXU-yU-eST" firstAttribute="trailing" secondItem="ybL-UG-dwT" secondAttribute="trailing" id="eBc-6g-nWr"/>
|
||||
<constraint firstItem="NXU-yU-eST" firstAttribute="top" secondItem="QJp-6C-yoZ" secondAttribute="bottom" id="mnZ-WQ-LX8"/>
|
||||
<constraint firstItem="NXU-yU-eST" firstAttribute="bottom" secondItem="ybL-UG-dwT" secondAttribute="bottom" id="vFS-tG-E43"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QiY-Mm-Dej" userLabel="Details">
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<string key="text">Start: 1970-01-01 01:00
|
||||
End: 1970-01-01 02:00
|
||||
Duration: 60:00</string>
|
||||
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<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>
|
||||
<constraints>
|
||||
<constraint firstItem="ybL-UG-dwT" firstAttribute="width" secondItem="xdn-EU-IMx" secondAttribute="width" id="PUH-xO-ZbD"/>
|
||||
<constraint firstItem="QiY-Mm-Dej" firstAttribute="width" secondItem="xdn-EU-IMx" secondAttribute="width" id="U6e-10-j55"/>
|
||||
<constraint firstItem="Guy-Ra-fpS" firstAttribute="width" secondItem="xdn-EU-IMx" secondAttribute="width" id="ZCJ-ol-1Jv"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<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="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"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="appDeveloper" destination="l8O-Kw-uc8" id="dfg-s6-Biz"/>
|
||||
<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"/>
|
||||
<outlet property="inputNotes" destination="NXU-yU-eST" id="c2n-cG-aLq"/>
|
||||
<outlet property="noteBottom" destination="vFS-tG-E43" id="Bxh-Tl-E2U"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="KN7-F1-BOL" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
<tapGestureRecognizer id="Jab-q2-U9X">
|
||||
<connections>
|
||||
<segue destination="qNp-w1-7Md" kind="modal" id="22y-Dy-3xf"/>
|
||||
</connections>
|
||||
</tapGestureRecognizer>
|
||||
<tapGestureRecognizer id="B0n-l6-MKc">
|
||||
<connections>
|
||||
<action selector="hideKeyboard" destination="VRk-wv-rhk" id="eMC-Nn-xoE"/>
|
||||
</connections>
|
||||
</tapGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="700" y="700"/>
|
||||
</scene>
|
||||
<!--App Search-->
|
||||
<scene sceneID="n6R-Wm-XxF">
|
||||
<objects>
|
||||
<tableViewController id="qNp-w1-7Md" customClass="TVCAppSearch" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="WYZ-wA-6Rh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="369.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<searchBar key="tableHeaderView" contentMode="redraw" preservesSuperviewLayoutMargins="YES" placeholder="Search AppStore" showsCancelButton="YES" id="9dl-ZI-85h">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="56"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<textInputTraits key="textInputTraits" returnKeyType="search" enablesReturnKeyAutomatically="YES"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="qNp-w1-7Md" id="vcp-BS-7xF"/>
|
||||
</connections>
|
||||
</searchBar>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="AppStoreSearchCell" textLabel="yIh-WB-0rK" detailTextLabel="PDe-1x-vle" style="IBUITableViewCellStyleSubtitle" id="Q8M-0Q-Mc7">
|
||||
<rect key="frame" x="0.0" y="84" width="320" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Q8M-0Q-Mc7" id="BPc-V2-I7v">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="yIh-WB-0rK">
|
||||
<rect key="frame" x="16" y="9" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="PDe-1x-vle">
|
||||
<rect key="frame" x="16" y="32.5" width="33" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="qNp-w1-7Md" id="Jdv-03-iVJ"/>
|
||||
<outlet property="delegate" destination="qNp-w1-7Md" id="3vD-b3-Ake"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<connections>
|
||||
<outlet property="searchBar" destination="9dl-ZI-85h" id="8Zr-E0-mzs"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="PhN-mC-C3W" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1400" y="700"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<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>
|
||||
506
main/GUI/Base.lproj/Requests.storyboard
Normal file
@@ -0,0 +1,506 @@
|
||||
<?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="pdd-aM-sKl">
|
||||
<device id="retina4_0" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Domains-->
|
||||
<scene sceneID="MN1-aZ-cZt">
|
||||
<objects>
|
||||
<tableViewController id="pdd-aM-sKl" customClass="TVCDomains" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="kj3-8X-TyT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="519"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="default" accessoryType="disclosureIndicator" hidesAccessoryWhenEditing="NO" indentationWidth="10" reuseIdentifier="DomainCell" textLabel="0HB-5f-eB1" detailTextLabel="MRe-Eq-gvc" style="IBUITableViewCellStyleSubtitle" id="F8D-aK-j1W">
|
||||
<rect key="frame" x="0.0" y="28" width="320" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="F8D-aK-j1W" id="FY2-xr-hqh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" id="0HB-5f-eB1">
|
||||
<rect key="frame" x="16" y="9" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="MRe-Eq-gvc">
|
||||
<rect key="frame" x="16" y="32.5" width="44" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="WcC-nb-Vf5" kind="push" id="EVQ-hO-JE9"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="pdd-aM-sKl" id="4fX-iP-7Oa"/>
|
||||
<outlet property="delegate" destination="pdd-aM-sKl" id="3RN-az-SYU"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Domains" id="nY5-jL-QT9">
|
||||
<leftBarButtonItems>
|
||||
<barButtonItem image="filter-clear" id="FZm-Ld-jJE">
|
||||
<connections>
|
||||
<action selector="filterButtonTapped:" destination="pdd-aM-sKl" id="Xyy-LF-eCF"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem enabled="NO" title="7 days" id="wxA-bC-1pN"/>
|
||||
</leftBarButtonItems>
|
||||
</navigationItem>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
<simulatedTabBarMetrics key="simulatedBottomBarMetrics" translucent="NO"/>
|
||||
<connections>
|
||||
<outlet property="filterButton" destination="FZm-Ld-jJE" id="g96-Q2-cYX"/>
|
||||
<outlet property="filterButtonDetail" destination="wxA-bC-1pN" id="CgP-oz-aXa"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="jfx-iA-E0v" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="0.0" y="0.0"/>
|
||||
</scene>
|
||||
<!--Hosts-->
|
||||
<scene sceneID="ZCV-Yx-jjW">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="requestsHosts" id="WcC-nb-Vf5" customClass="TVCHosts" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="nRF-dc-dC2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="519"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<containerView key="tableHeaderView" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kh4-PQ-hy6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<connections>
|
||||
<segue destination="1ba-SA-8sT" kind="embed" id="vf1-07-AS4"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="HostCell" textLabel="Rnk-SP-UHm" detailTextLabel="ovQ-lJ-hWJ" style="IBUITableViewCellStyleSubtitle" id="uv0-9B-Zbb">
|
||||
<rect key="frame" x="0.0" y="77" width="320" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="uv0-9B-Zbb" id="6vH-Du-gCg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Rnk-SP-UHm">
|
||||
<rect key="frame" x="16" y="9" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ovQ-lJ-hWJ">
|
||||
<rect key="frame" x="16" y="32.5" width="44" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="h7Z-Qr-pJ5" kind="push" id="TPa-Zn-eOs"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="WcC-nb-Vf5" id="szM-iI-Jgi"/>
|
||||
<outlet property="delegate" destination="WcC-nb-Vf5" id="sBd-BW-Wg6"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Hosts" prompt="com.app.Example" id="TvD-8U-F05"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Gdi-Xi-JUL" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="700" y="0.0"/>
|
||||
</scene>
|
||||
<!--Occurrences-->
|
||||
<scene sceneID="ws3-sK-l8m">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="requestsOccurrences" id="h7Z-Qr-pJ5" customClass="TVCHostDetails" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="4ms-FO-Fge">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="519"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<containerView key="tableHeaderView" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SxM-2c-aJb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<connections>
|
||||
<segue destination="1ba-SA-8sT" kind="embed" id="ueN-6L-cP7"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="HostDetailCell" textLabel="J2P-mU-Vad" detailTextLabel="eWb-mX-udN" style="IBUITableViewCellStyleValue1" id="ZCA-Dz-i92">
|
||||
<rect key="frame" x="0.0" y="77" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ZCA-Dz-i92" id="nxe-48-jAQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" id="J2P-mU-Vad">
|
||||
<rect key="frame" x="16" y="12" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="eWb-mX-udN">
|
||||
<rect key="frame" x="241" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="rjy-Di-Cru" kind="push" id="SfC-iY-Ce0"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="h7Z-Qr-pJ5" id="fyW-Av-fWY"/>
|
||||
<outlet property="delegate" destination="h7Z-Qr-pJ5" id="gBq-jA-u5V"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Occurrences" prompt="com.domain.network.cdn" id="bys-2u-rHs"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="UxH-PH-KQy" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1400" y="0.0"/>
|
||||
</scene>
|
||||
<!--Occurrence Context-->
|
||||
<scene sceneID="A1T-7G-agr">
|
||||
<objects>
|
||||
<tableViewController id="rjy-Di-Cru" customClass="TVCOccurrenceContext" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="EfM-yv-85f">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="519"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="OccurrenceContextCell" textLabel="xgq-hW-e3R" detailTextLabel="No8-Bf-ptL" style="IBUITableViewCellStyleValue2" id="KQh-Ei-If8">
|
||||
<rect key="frame" x="0.0" y="28" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="KQh-Ei-If8" id="i32-u4-1Q8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="xgq-hW-e3R">
|
||||
<rect key="frame" x="16" y="12" width="91" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" id="No8-Bf-ptL">
|
||||
<rect key="frame" x="113" y="12" width="59" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="rjy-Di-Cru" id="6CT-Vd-Ixn"/>
|
||||
<outlet property="delegate" destination="rjy-Di-Cru" id="JtY-um-ZTF"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Occurrence Context" id="2mj-It-uND">
|
||||
<barButtonItem key="rightBarButtonItem" image="jump-to-target" id="TqX-qO-B3s">
|
||||
<connections>
|
||||
<action selector="jumpToTsZero" destination="rjy-Di-Cru" id="RS6-IO-hi4"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="cYd-oX-akc" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2100" y="0.0"/>
|
||||
</scene>
|
||||
<!--Date Filter-->
|
||||
<scene sceneID="GqC-c0-bWe">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="domainFilter" modalTransitionStyle="crossDissolve" id="r7v-PM-PrR" customClass="VCDateFilter" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="QBv-5g-BTH">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<navigationBar hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jAM-LN-evh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<items>
|
||||
<navigationItem title="Placeholder" id="s5o-aw-nIo">
|
||||
<barButtonItem key="leftBarButtonItem" title="Item" image="filter-clear" id="oMW-R3-3Eh"/>
|
||||
</navigationItem>
|
||||
</items>
|
||||
</navigationBar>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pEc-vv-7Ts">
|
||||
<rect key="frame" x="8" y="64" width="233.5" height="391.5"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="UNT-qn-2cg">
|
||||
<rect key="frame" x="8" y="8" width="217.5" height="32"/>
|
||||
<segments>
|
||||
<segment title="Most recent"/>
|
||||
<segment title="Date Range"/>
|
||||
</segments>
|
||||
<connections>
|
||||
<action selector="didChangeFilterBy:" destination="r7v-PM-PrR" eventType="valueChanged" id="kM6-QE-ZGV"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="gEf-Ra-RyA">
|
||||
<rect key="frame" x="10" y="47" width="213.5" height="334.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Show entries no older than" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UBq-oH-pKp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="213.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ucF-MH-iRP">
|
||||
<rect key="frame" x="0.0" y="35.5" width="213.5" height="50"/>
|
||||
<subviews>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="qhe-6d-hGB">
|
||||
<rect key="frame" x="-2" y="0.0" width="155.5" height="51"/>
|
||||
<connections>
|
||||
<action selector="durationSliderChanged:" destination="r7v-PM-PrR" eventType="valueChanged" id="nQB-w9-dY1"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="7 days" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ika-su-PZQ">
|
||||
<rect key="frame" x="159.5" y="16" width="54" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="ika-su-PZQ" secondAttribute="height" multiplier="3" id="o9m-cy-kkn"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="qhe-6d-hGB" firstAttribute="leading" secondItem="ucF-MH-iRP" secondAttribute="leading" id="EGU-ln-lJF"/>
|
||||
<constraint firstItem="ika-su-PZQ" firstAttribute="trailing" secondItem="ucF-MH-iRP" secondAttribute="trailing" id="KLs-Ft-wSo"/>
|
||||
<constraint firstItem="qhe-6d-hGB" firstAttribute="bottom" secondItem="ucF-MH-iRP" secondAttribute="bottom" id="PTR-Et-Klv"/>
|
||||
<constraint firstItem="ika-su-PZQ" firstAttribute="leading" secondItem="qhe-6d-hGB" secondAttribute="trailing" constant="8" symbolic="YES" id="aHy-IX-X4B"/>
|
||||
<constraint firstItem="ika-su-PZQ" firstAttribute="centerY" secondItem="qhe-6d-hGB" secondAttribute="centerY" id="cHs-5z-CHK"/>
|
||||
<constraint firstItem="qhe-6d-hGB" firstAttribute="top" secondItem="ucF-MH-iRP" secondAttribute="top" id="eJC-d4-zg0"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Show entries within range" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rtf-o1-gk6">
|
||||
<rect key="frame" x="0.0" y="100.5" width="213.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9As-hA-MKt">
|
||||
<rect key="frame" x="0.0" y="136" width="213.5" height="74"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="From:" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wAd-o2-PHY">
|
||||
<rect key="frame" x="0.0" y="6.5" width="44" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FVD-kB-91w">
|
||||
<rect key="frame" x="52" y="0.0" width="161.5" height="33"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<state key="normal" title="1970-01-01 01:00"/>
|
||||
<connections>
|
||||
<action selector="didTapRangeButton:" destination="r7v-PM-PrR" eventType="touchUpInside" id="g05-Sc-0P2"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="To:" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fzL-94-c0l">
|
||||
<rect key="frame" x="0.0" y="47.5" width="44" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IG3-Wc-UI4">
|
||||
<rect key="frame" x="52" y="41" width="161.5" height="33"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<state key="normal" title="1970-01-01 01:00"/>
|
||||
<connections>
|
||||
<action selector="didTapRangeButton:" destination="r7v-PM-PrR" eventType="touchUpInside" id="63v-sy-Bpo"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="FVD-kB-91w" firstAttribute="top" secondItem="9As-hA-MKt" secondAttribute="top" id="J2e-NO-NUD"/>
|
||||
<constraint firstItem="IG3-Wc-UI4" firstAttribute="top" secondItem="FVD-kB-91w" secondAttribute="bottom" constant="8" symbolic="YES" id="Jyg-b6-5rH"/>
|
||||
<constraint firstItem="fzL-94-c0l" firstAttribute="centerY" secondItem="IG3-Wc-UI4" secondAttribute="centerY" id="LAG-jg-nBi"/>
|
||||
<constraint firstItem="wAd-o2-PHY" firstAttribute="trailing" secondItem="fzL-94-c0l" secondAttribute="trailing" id="Lav-7y-PUJ"/>
|
||||
<constraint firstItem="IG3-Wc-UI4" firstAttribute="trailing" secondItem="9As-hA-MKt" secondAttribute="trailing" id="XqI-1p-FNv"/>
|
||||
<constraint firstItem="FVD-kB-91w" firstAttribute="leading" secondItem="wAd-o2-PHY" secondAttribute="trailing" constant="8" symbolic="YES" id="YlE-w7-CZ5"/>
|
||||
<constraint firstItem="FVD-kB-91w" firstAttribute="trailing" secondItem="9As-hA-MKt" secondAttribute="trailing" id="a6D-1D-HvF"/>
|
||||
<constraint firstItem="wAd-o2-PHY" firstAttribute="leading" secondItem="fzL-94-c0l" secondAttribute="leading" id="bM0-gJ-IW5"/>
|
||||
<constraint firstItem="wAd-o2-PHY" firstAttribute="centerY" secondItem="FVD-kB-91w" secondAttribute="centerY" id="g7F-LP-PQQ"/>
|
||||
<constraint firstItem="IG3-Wc-UI4" firstAttribute="bottom" secondItem="9As-hA-MKt" secondAttribute="bottom" priority="750" id="jlK-69-8hl"/>
|
||||
<constraint firstItem="IG3-Wc-UI4" firstAttribute="leading" secondItem="fzL-94-c0l" secondAttribute="trailing" constant="8" symbolic="YES" id="pcE-Gv-oj7"/>
|
||||
<constraint firstItem="wAd-o2-PHY" firstAttribute="leading" secondItem="9As-hA-MKt" secondAttribute="leading" id="zgR-pJ-vFs"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Order by" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9Fe-5F-TVt">
|
||||
<rect key="frame" x="0.0" y="225" width="213.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cWy-un-IHC">
|
||||
<rect key="frame" x="0.0" y="260.5" width="213.5" height="74"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="UKE-MR-kRJ">
|
||||
<rect key="frame" x="-2" y="0.0" width="217.5" height="36"/>
|
||||
<segments>
|
||||
<segment title="Date"/>
|
||||
<segment title="Name"/>
|
||||
<segment title="Count"/>
|
||||
</segments>
|
||||
</segmentedControl>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="eG2-a4-zm5">
|
||||
<rect key="frame" x="-2" y="43" width="217.5" height="32"/>
|
||||
<segments>
|
||||
<segment title="Ascending"/>
|
||||
<segment title="Descending"/>
|
||||
</segments>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="eG2-a4-zm5" firstAttribute="top" secondItem="UKE-MR-kRJ" secondAttribute="bottom" constant="8" symbolic="YES" id="6oC-bZ-XdM"/>
|
||||
<constraint firstItem="eG2-a4-zm5" firstAttribute="leading" secondItem="cWy-un-IHC" secondAttribute="leading" constant="-2" id="7R0-qB-J0u"/>
|
||||
<constraint firstAttribute="bottom" secondItem="eG2-a4-zm5" secondAttribute="bottom" id="JbN-vA-Rd5"/>
|
||||
<constraint firstItem="UKE-MR-kRJ" firstAttribute="top" secondItem="cWy-un-IHC" secondAttribute="top" id="L21-Kf-g2d"/>
|
||||
<constraint firstAttribute="trailing" secondItem="eG2-a4-zm5" secondAttribute="trailing" constant="-2" id="cbD-H9-e1Q"/>
|
||||
<constraint firstItem="UKE-MR-kRJ" firstAttribute="leading" secondItem="cWy-un-IHC" secondAttribute="leading" constant="-2" id="lKB-g4-asw"/>
|
||||
<constraint firstAttribute="trailing" secondItem="UKE-MR-kRJ" secondAttribute="trailing" constant="-2" id="xIa-X2-0Lp"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="UNT-qn-2cg" firstAttribute="top" secondItem="pEc-vv-7Ts" secondAttribute="top" constant="8" id="Awu-uv-9wF"/>
|
||||
<constraint firstItem="UNT-qn-2cg" firstAttribute="leading" secondItem="pEc-vv-7Ts" secondAttribute="leading" constant="8" id="Icx-YR-5bc"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="top" secondItem="UNT-qn-2cg" secondAttribute="bottom" constant="8" symbolic="YES" id="QPi-aa-6ff"/>
|
||||
<constraint firstItem="UNT-qn-2cg" firstAttribute="trailing" secondItem="pEc-vv-7Ts" secondAttribute="trailing" constant="-8" id="Sof-6L-T2D"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="bottom" secondItem="pEc-vv-7Ts" secondAttribute="bottom" constant="-10" id="TMx-5J-z2P"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="leading" secondItem="pEc-vv-7Ts" secondAttribute="leading" constant="10" id="U6l-7M-bm4"/>
|
||||
<constraint firstItem="gEf-Ra-RyA" firstAttribute="trailing" secondItem="pEc-vv-7Ts" secondAttribute="trailing" constant="-10" id="YKE-TR-fTB"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="12"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sAi-8j-0n1" customClass="PopupTriangle" customModule="AppCheck" customModuleProvider="target">
|
||||
<rect key="frame" x="14" y="46" width="28" height="22"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="22" id="MaD-aD-U8h"/>
|
||||
<constraint firstAttribute="width" constant="28" id="ntP-rP-UMh"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="color" keyPath="color">
|
||||
<color key="value" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.20000000000000001" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstItem="sAi-8j-0n1" firstAttribute="bottom" secondItem="pEc-vv-7Ts" secondAttribute="top" constant="4" id="DCq-Ps-sQo"/>
|
||||
<constraint firstItem="pEc-vv-7Ts" firstAttribute="top" secondItem="jAM-LN-evh" secondAttribute="bottom" constant="20" id="EdA-nv-DEa"/>
|
||||
<constraint firstItem="pEc-vv-7Ts" firstAttribute="leading" secondItem="u0F-hK-vVD" secondAttribute="leading" constant="8" id="Iow-GV-Lxy"/>
|
||||
<constraint firstItem="jAM-LN-evh" firstAttribute="trailing" secondItem="u0F-hK-vVD" secondAttribute="trailing" id="Lju-K6-G89"/>
|
||||
<constraint firstItem="jAM-LN-evh" firstAttribute="top" secondItem="u0F-hK-vVD" secondAttribute="top" id="MqW-YU-POp"/>
|
||||
<constraint firstItem="u0F-hK-vVD" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="pEc-vv-7Ts" secondAttribute="trailing" constant="8" id="V9T-2Y-oNy"/>
|
||||
<constraint firstItem="sAi-8j-0n1" firstAttribute="leading" secondItem="pEc-vv-7Ts" secondAttribute="leading" constant="6" id="cXH-3c-s6t"/>
|
||||
<constraint firstItem="jAM-LN-evh" firstAttribute="leading" secondItem="u0F-hK-vVD" secondAttribute="leading" id="ula-eW-vAq"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="u0F-hK-vVD"/>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="oRi-17-cVM" appends="YES" id="2pk-5e-eJD"/>
|
||||
</connections>
|
||||
</view>
|
||||
<extendedEdge key="edgesForExtendedLayout" bottom="YES"/>
|
||||
<connections>
|
||||
<outlet property="buttonRangeEnd" destination="IG3-Wc-UI4" id="wAd-ca-bVQ"/>
|
||||
<outlet property="buttonRangeStart" destination="FVD-kB-91w" id="HbX-Vl-uBE"/>
|
||||
<outlet property="durationLabel" destination="ika-su-PZQ" id="1Br-vu-xir"/>
|
||||
<outlet property="durationSlider" destination="qhe-6d-hGB" id="wph-zX-WIz"/>
|
||||
<outlet property="durationTitle" destination="UBq-oH-pKp" id="BEd-Lo-a2v"/>
|
||||
<outlet property="durationView" destination="ucF-MH-iRP" id="TCI-Pp-drf"/>
|
||||
<outlet property="filterBy" destination="UNT-qn-2cg" id="M1J-n8-LHq"/>
|
||||
<outlet property="orderbyAsc" destination="eG2-a4-zm5" id="II1-hc-pyZ"/>
|
||||
<outlet property="orderbyType" destination="UKE-MR-kRJ" id="fK7-dW-MLd"/>
|
||||
<outlet property="rangeTitle" destination="rtf-o1-gk6" id="2DY-xP-VOg"/>
|
||||
<outlet property="rangeView" destination="9As-hA-MKt" id="0Mq-Gi-nF6"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="xTS-RW-xLN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
<tapGestureRecognizer id="oRi-17-cVM">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="r7v-PM-PrR" id="JME-7W-w51"/>
|
||||
</connections>
|
||||
</tapGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="0.0" y="-700"/>
|
||||
</scene>
|
||||
<!--Analysis Bar-->
|
||||
<scene sceneID="1qq-WD-Lqq">
|
||||
<objects>
|
||||
<viewController id="1ba-SA-8sT" customClass="VCAnalysisBar" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="qp6-er-N6U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tabBar contentMode="scaleToFill" translucent="NO" id="1Jy-zg-CXR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<items>
|
||||
<tabBarItem title="Co-Occurrence" image="intersection" id="KXh-kQ-rAF"/>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="1ba-SA-8sT" id="bRS-kh-dOv"/>
|
||||
</connections>
|
||||
</tabBar>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="dtz-KG-P4C"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="tabBar" destination="1Jy-zg-CXR" id="VTV-xq-Aou"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="XnK-B9-RSJ" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="700" y="-700"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="vf1-07-AS4"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
<resources>
|
||||
<image name="filter-clear" width="20" height="20"/>
|
||||
<image name="intersection" width="25" height="25"/>
|
||||
<image name="jump-to-target" width="20" height="20"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -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" useSafeAreas="YES" colorMatched="YES" initialViewController="qdB-ZO-LHY">
|
||||
<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="qdB-ZO-LHY">
|
||||
<device id="retina4_0" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
@@ -243,25 +243,93 @@
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Advanced Settings" id="Vlg-nm-VB3">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="VnR-9B-1zl">
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="rio-m6-pXN" detailTextLabel="rr4-sR-VxD" style="IBUITableViewCellStyleSubtitle" id="pQ5-lm-Rco">
|
||||
<rect key="frame" x="0.0" y="687.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VnR-9B-1zl" id="ZTz-vZ-l5p">
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="pQ5-lm-Rco" id="52Y-H3-jvJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="twS-Ne-dU0">
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Force disconnect unresolvable" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="rio-m6-pXN">
|
||||
<rect key="frame" x="16" y="5" width="234" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="in case DNS returns empty IP" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="rr4-sR-VxD">
|
||||
<rect key="frame" x="16" y="25.5" width="166" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="TSu-IP-KFG">
|
||||
<rect key="frame" x="257" y="6" width="49" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="togglePreventUnresolvable:" destination="qdB-ZO-LHY" eventType="valueChanged" id="xKv-Hp-Nyq"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="accessoryView" destination="TSu-IP-KFG" id="VGm-hN-DLK"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="x5u-5T-XTO" detailTextLabel="n7p-Ab-69G" style="IBUITableViewCellStyleSubtitle" id="lxs-NQ-Q7V">
|
||||
<rect key="frame" x="0.0" y="731.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="lxs-NQ-Q7V" id="B1l-cb-yQg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Force disconnect swcd" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="x5u-5T-XTO">
|
||||
<rect key="frame" x="16" y="5" width="177" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="background deamon User-Agent: swcd" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="n7p-Ab-69G">
|
||||
<rect key="frame" x="16" y="25.5" width="220.5" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="c5N-PN-qWU">
|
||||
<rect key="frame" x="257" y="6" width="49" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="togglePreventSWCD:" destination="qdB-ZO-LHY" eventType="valueChanged" id="MAa-5v-djq"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="accessoryView" destination="c5N-PN-qWU" id="SAN-Q9-VOn"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Tgc-re-gI7">
|
||||
<rect key="frame" x="0.0" y="775.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Tgc-re-gI7" id="haV-RB-dEa">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vZa-EO-FAZ">
|
||||
<rect key="frame" x="125" y="7" width="70" height="30"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<state key="normal" title="Export DB"/>
|
||||
<connections>
|
||||
<action selector="exportDB" destination="qdB-ZO-LHY" eventType="touchUpInside" id="FYN-Zz-UK4"/>
|
||||
<action selector="exportDB" destination="qdB-ZO-LHY" eventType="touchUpInside" id="NPx-9w-ua0"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="twS-Ne-dU0" firstAttribute="centerY" secondItem="ZTz-vZ-l5p" secondAttribute="centerY" id="LgK-8q-r6K"/>
|
||||
<constraint firstItem="twS-Ne-dU0" firstAttribute="centerX" secondItem="ZTz-vZ-l5p" secondAttribute="centerX" id="ltC-Ba-Bxr"/>
|
||||
<constraint firstItem="vZa-EO-FAZ" firstAttribute="centerX" secondItem="haV-RB-dEa" secondAttribute="centerX" id="h0M-qz-pHV"/>
|
||||
<constraint firstItem="vZa-EO-FAZ" firstAttribute="centerY" secondItem="haV-RB-dEa" secondAttribute="centerY" id="xBJ-94-nJh"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
@@ -282,6 +350,8 @@
|
||||
<outlet property="cellNotificationConnectionAlert" destination="OTC-Kt-LFT" id="XiG-CC-4lC"/>
|
||||
<outlet property="cellNotificationReminder" destination="jZA-aP-aHG" id="sjo-2s-rqW"/>
|
||||
<outlet property="cellPrivacyAutoDelete" destination="Qyy-0U-yhd" id="PzN-iv-kFl"/>
|
||||
<outlet property="swcdToggle" destination="c5N-PN-qWU" id="qDy-BX-O85"/>
|
||||
<outlet property="unresolvableToggle" destination="TSu-IP-KFG" id="Vdb-cm-Uy2"/>
|
||||
<outlet property="vpnToggle" destination="ZAz-WT-FDb" id="lGX-J8-WrU"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
@@ -293,12 +363,12 @@
|
||||
<scene sceneID="218-uP-X7b">
|
||||
<objects>
|
||||
<tableViewController id="q3B-Yi-1bx" customClass="TVCFilter" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="GSg-ZZ-F8J">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="GSg-ZZ-F8J">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="DomainFilterCell" textLabel="MrS-rb-RLB" style="IBUITableViewCellStyleDefault" id="EO2-ww-xuz">
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="DomainFilterCell" textLabel="MrS-rb-RLB" style="IBUITableViewCellStyleDefault" id="EO2-ww-xuz">
|
||||
<rect key="frame" x="0.0" y="28" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="EO2-ww-xuz" id="AtR-ce-uYs">
|
||||
@@ -364,7 +434,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" id="zaV-mh-eqb">
|
||||
<rect key="frame" x="252" y="6" width="54" height="31"/>
|
||||
<rect key="frame" x="250" y="6" width="54" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleAllowRestartReminder:" destination="JYM-cs-i4H" eventType="valueChanged" id="F4e-k2-bni"/>
|
||||
@@ -391,7 +461,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" id="HaE-En-NH3">
|
||||
<rect key="frame" x="252" y="6" width="54" height="31"/>
|
||||
<rect key="frame" x="250" y="6" width="54" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleAllowRestartNotify:" destination="JYM-cs-i4H" eventType="valueChanged" id="12C-h5-mrR"/>
|
||||
@@ -418,7 +488,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" id="N2Q-cU-pkd">
|
||||
<rect key="frame" x="252" y="6" width="54" height="31"/>
|
||||
<rect key="frame" x="250" y="6" width="54" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleAllowRestartBadge:" destination="JYM-cs-i4H" eventType="valueChanged" id="76l-6y-fOu"/>
|
||||
@@ -476,7 +546,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mTm-Rm-1RQ">
|
||||
<rect key="frame" x="255" y="6" width="49" height="31"/>
|
||||
<rect key="frame" x="255" y="6" width="48" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleAllowRecordingReminder:" destination="JYM-cs-i4H" eventType="valueChanged" id="unC-Ur-jPM"/>
|
||||
@@ -602,7 +672,7 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="who-8G-voz">
|
||||
<rect key="frame" x="256" y="6" width="48" height="31"/>
|
||||
<rect key="frame" x="256" y="6" width="47" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleShowNotifications:" destination="D2a-Po-vDU" eventType="valueChanged" id="Thg-6R-7wM"/>
|
||||
@@ -832,6 +902,6 @@ If App Badge is enabled, display the letter "1" on the homescreen app icon, as l
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="tUF-Kv-koO"/>
|
||||
<segue reference="6wc-d1-VYY"/>
|
||||
<segue reference="EzT-Xq-wka"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
@@ -157,4 +157,16 @@ struct VPNAppMessage {
|
||||
static func notificationSettingsChanged() -> Self {
|
||||
.init("notify-prefs-change:1")
|
||||
}
|
||||
/// Triggered whenever user taps on the start/stop recording button
|
||||
static func isRecording(_ state: CurrentRecordingState) -> Self {
|
||||
.init("recording-now:\(state.rawValue)")
|
||||
}
|
||||
/// Triggered whenever user taps on the switch in settings
|
||||
static func disconnectUnresolvable(_ state: Bool) -> Self {
|
||||
.init("disconnect-unresolvable:\(state ? 1 : 0)")
|
||||
}
|
||||
/// Triggered whenever user taps on the switch in settings
|
||||
static func disconnectSWCD(_ state: Bool) -> Self {
|
||||
.init("disconnect-swcd:\(state ? 1 : 0)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ class GlassVPNHook {
|
||||
private var filterOptions: [(block: Bool, ignore: Bool, customA: Bool, customB: Bool)]!
|
||||
private var autoDeleteTimer: Timer? = nil
|
||||
private var cachedNotify: CachedConnectionAlert!
|
||||
private var currentlyRecording: Bool = false
|
||||
|
||||
public var forceDisconnectUnresolvable: Bool = false
|
||||
public var forceDisconnectSWCD: Bool = false
|
||||
|
||||
|
||||
init() { reset() }
|
||||
|
||||
@@ -16,6 +21,9 @@ class GlassVPNHook {
|
||||
reloadDomainFilter()
|
||||
setAutoDelete(PrefsShared.AutoDeleteLogsDays)
|
||||
cachedNotify = CachedConnectionAlert()
|
||||
currentlyRecording = PrefsShared.CurrentlyRecording != .Off
|
||||
forceDisconnectUnresolvable = PrefsShared.ForceDisconnectUnresolvableDNS
|
||||
forceDisconnectSWCD = PrefsShared.ForceDisconnectSWCD
|
||||
}
|
||||
|
||||
/// Invalidate auto-delete timer and release stored properties. You should nullify this instance afterwards.
|
||||
@@ -25,6 +33,9 @@ class GlassVPNHook {
|
||||
autoDeleteTimer?.fire() // one last time before we quit
|
||||
autoDeleteTimer?.invalidate()
|
||||
cachedNotify = nil
|
||||
currentlyRecording = false
|
||||
forceDisconnectUnresolvable = false
|
||||
forceDisconnectSWCD = false
|
||||
}
|
||||
|
||||
/// Call this method from `PacketTunnelProvider.handleAppMessage(_:completionHandler:)`
|
||||
@@ -43,6 +54,16 @@ class GlassVPNHook {
|
||||
case "notify-prefs-change":
|
||||
cachedNotify = CachedConnectionAlert()
|
||||
return
|
||||
case "recording-now":
|
||||
let newState = CurrentRecordingState(rawValue: Int(value) ?? 0)
|
||||
currentlyRecording = newState != .Off
|
||||
return
|
||||
case "disconnect-unresolvable":
|
||||
forceDisconnectUnresolvable = value == "1"
|
||||
return
|
||||
case "disconnect-swcd":
|
||||
forceDisconnectSWCD = value == "1"
|
||||
return
|
||||
default: break
|
||||
}
|
||||
}
|
||||
@@ -57,18 +78,24 @@ class GlassVPNHook {
|
||||
/// - Returns: `true` if the request shoud be blocked.
|
||||
func processDNSRequest(_ domain: String) -> Bool {
|
||||
let i = filterIndex(for: domain)
|
||||
// TODO: disable ignore & block during recordings
|
||||
let (block, ignore, cA, cB) = (i<0) ? (false, false, false, false) : filterOptions[i]
|
||||
if ignore {
|
||||
if ignore, !currentlyRecording {
|
||||
return block
|
||||
}
|
||||
let blockActive = block && !currentlyRecording
|
||||
queue.async {
|
||||
do { try AppDB?.logWrite(domain, blocked: block) }
|
||||
do { try AppDB?.logWrite(domain, blocked: blockActive) }
|
||||
catch { NSLog("[VPN.WARN] Couldn't write: \(error)") }
|
||||
}
|
||||
// TODO: disable notifications during recording?
|
||||
cachedNotify.postOrIgnore(domain, blck: block, custA: cA, custB: cB)
|
||||
// TODO: wait for notify response to block or allow connection
|
||||
return block
|
||||
return blockActive
|
||||
}
|
||||
|
||||
func silentlyPrevented(_ domain: String) {
|
||||
// TODO: persist in a separate db/table?
|
||||
NSLog("[VPN.INFO] preventing connection to \(domain)")
|
||||
}
|
||||
|
||||
/// Build binary tree for reverse DNS lookup
|
||||
@@ -125,13 +152,12 @@ class GlassVPNHook {
|
||||
@objc private func autoDeleteNow(_ sender: Timer) {
|
||||
NSLog("[VPN.INFO] Auto-delete old logs")
|
||||
queue.async {
|
||||
guard sender.isValid else { return }
|
||||
do {
|
||||
try AppDB?.dnsLogsDeleteOlderThan(days: sender.userInfo as! Int)
|
||||
} catch {
|
||||
NSLog("[VPN.WARN] Couldn't delete logs, will retry in 5 minutes. \(error)")
|
||||
if sender.isValid {
|
||||
sender.fireDate = Date().addingTimeInterval(300) // retry in 5 min
|
||||
}
|
||||
sender.fireDate = Date().addingTimeInterval(300) // retry in 5 min
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
main/Recordings/App Icons/AppStoreSearch.swift
Normal file
@@ -0,0 +1,52 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension URL {
|
||||
static func appStoreSearch(query: String) -> URL {
|
||||
// https://itunes.apple.com/lookup?bundleId=...
|
||||
URL.make("https://itunes.apple.com/search", params: [
|
||||
"media" : "software",
|
||||
"limit" : "25",
|
||||
"country" : NSLocale.current.regionCode ?? "DE",
|
||||
"version" : "2",
|
||||
"term" : query,
|
||||
])!
|
||||
}
|
||||
}
|
||||
|
||||
struct AppStoreSearch {
|
||||
struct Result {
|
||||
let bundleId, name: String
|
||||
let developer, imageURL: String?
|
||||
}
|
||||
|
||||
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, error ?? URLError(.badServerResponse))
|
||||
return
|
||||
}
|
||||
closure(jsonSearchToList(data), nil)
|
||||
}.resume()
|
||||
}
|
||||
|
||||
private static func jsonSearchToList(_ data: Data) -> [Result]? {
|
||||
guard let json = (try? JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed)) as? [String: Any],
|
||||
let resAll = json["results"] as? [Any] else {
|
||||
return nil
|
||||
}
|
||||
return resAll.compactMap {
|
||||
guard let res = $0 as? [String: Any],
|
||||
let bndl = res["bundleId"] as? String,
|
||||
let name = res["trackName"] as? String // trackCensoredName
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
let seller = res["sellerName"] as? String // artistName
|
||||
let image = res["artworkUrl60"] as? String // artworkUrl100
|
||||
return Result(bundleId: bndl, name: name, developer: seller, imageURL: image)
|
||||
}
|
||||
}
|
||||
}
|
||||
104
main/Recordings/App Icons/BundleIcon.swift
Normal file
@@ -0,0 +1,104 @@
|
||||
import UIKit
|
||||
|
||||
extension CGContext {
|
||||
func lineFromTo(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) {
|
||||
self.move(to: CGPoint(x: x1, y: y1))
|
||||
self.addLine(to: CGPoint(x: x2, y: y2))
|
||||
}
|
||||
}
|
||||
|
||||
struct BundleIcon {
|
||||
|
||||
static let unknown : UIImage? = {
|
||||
let rect = CGRect(x: 0, y: 0, width: 30, height: 30)
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
let lineWidth: CGFloat = 0.5
|
||||
let corner: CGFloat = 6.75
|
||||
let c = corner / CGFloat.pi + lineWidth/2
|
||||
let sz: CGFloat = rect.height
|
||||
let m = sz / 2
|
||||
let r1 = 0.2 * sz, r2 = sqrt(2 * r1 * r1)
|
||||
|
||||
// diagonal
|
||||
context.lineFromTo(x1: c, y1: c, x2: sz-c, y2: sz-c)
|
||||
context.lineFromTo(x1: c, y1: sz-c, x2: sz-c, y2: c)
|
||||
// horizontal
|
||||
context.lineFromTo(x1: 0, y1: m, x2: sz, y2: m)
|
||||
context.lineFromTo(x1: 0, y1: m + r1, x2: sz, y2: m + r1)
|
||||
context.lineFromTo(x1: 0, y1: m - r1, x2: sz, y2: m - r1)
|
||||
// vertical
|
||||
context.lineFromTo(x1: m, y1: 0, x2: m, y2: sz)
|
||||
context.lineFromTo(x1: m + r1, y1: 0, x2: m + r1, y2: sz)
|
||||
context.lineFromTo(x1: m - r1, y1: 0, x2: m - r1, y2: sz)
|
||||
// circles
|
||||
context.addEllipse(in: CGRect(x: m - r1, y: m - r1, width: 2*r1, height: 2*r1))
|
||||
context.addEllipse(in: CGRect(x: m - r2, y: m - r2, width: 2*r2, height: 2*r2))
|
||||
let r3 = CGRect(x: c, y: c, width: sz - 2*c, height: sz - 2*c)
|
||||
context.addEllipse(in: r3)
|
||||
context.addRect(r3)
|
||||
|
||||
UIColor.clear.setFill()
|
||||
UIColor.gray.setStroke()
|
||||
let rounded = UIBezierPath(roundedRect: rect.insetBy(dx: lineWidth/2, dy: lineWidth/2), cornerRadius: corner)
|
||||
rounded.lineWidth = lineWidth
|
||||
rounded.stroke()
|
||||
|
||||
let img = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return img
|
||||
}()
|
||||
|
||||
private static let apple : UIImage? = {
|
||||
let rect = CGRect(x: 0, y: 0, width: 30, height: 30)
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
|
||||
|
||||
// #colorLiteral(red: 0.5843137503, green: 0.8235294223, blue: 0.4196078479, alpha: 1).setFill()
|
||||
// UIBezierPath(roundedRect: rect, cornerRadius: 0).fill()
|
||||
// print("drawing")
|
||||
let fs = 36 as CGFloat
|
||||
let hFont = UIFont.systemFont(ofSize: fs)
|
||||
var attrib = [
|
||||
NSAttributedString.Key.font: hFont,
|
||||
NSAttributedString.Key.foregroundColor: UIColor.gray
|
||||
]
|
||||
|
||||
let str = "" as NSString
|
||||
let actualHeight = str.size(withAttributes: attrib).height
|
||||
attrib[NSAttributedString.Key.font] = hFont.withSize(fs * fs / actualHeight)
|
||||
|
||||
let strW = str.size(withAttributes: attrib).width
|
||||
str.draw(at: CGPoint(x: (rect.size.width - strW) / 2.0, y: -3), withAttributes: attrib)
|
||||
|
||||
let img = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return img
|
||||
}()
|
||||
|
||||
private static let cacheDir = URL.documentDir().appendingPathComponent("app-store-search-cache", isDirectory:true)
|
||||
|
||||
private static func local(_ bundleId: String) -> URL {
|
||||
cacheDir.appendingPathComponent("\(bundleId).img")
|
||||
}
|
||||
|
||||
static func initCache() {
|
||||
try? FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
|
||||
static func image(_ bundleId: String?, ifNotStored: (() -> Void)? = nil) -> UIImage? {
|
||||
guard let appId = bundleId else {
|
||||
return unknown
|
||||
}
|
||||
guard let data = try? Data(contentsOf: local(appId)),
|
||||
let img = UIImage(data: data, scale: 2.0) else {
|
||||
ifNotStored?()
|
||||
return appId.hasPrefix("com.apple.") ? apple : unknown
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
static func download(_ bundleId: String, url: URL, whenDone: @escaping () -> Void) -> URLSessionDownloadTask {
|
||||
return url.download(to: local(bundleId), onSuccess: whenDone)
|
||||
}
|
||||
}
|
||||
190
main/Recordings/TVCAppSearch.swift
Normal file
@@ -0,0 +1,190 @@
|
||||
import UIKit
|
||||
|
||||
protocol TVCAppSearchDelegate {
|
||||
func appSearch(didSelect bundleId: String, appName: String?, developer: String?)
|
||||
}
|
||||
|
||||
class TVCAppSearch: UITableViewController, UISearchBarDelegate {
|
||||
|
||||
private var dataSource: [AppStoreSearch.Result] = []
|
||||
private var dataSourceLocal: [AppBundleInfo] = []
|
||||
private var isLoading: Bool = false
|
||||
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() {
|
||||
super.viewDidLoad()
|
||||
BundleIcon.initCache()
|
||||
dataSourceLocal = AppDB?.appBundleList() ?? []
|
||||
}
|
||||
|
||||
override var keyCommands: [UIKeyCommand]? {
|
||||
[UIKeyCommand(input: UIKeyCommand.inputEscape, modifierFlags: [], action: #selector(closeThis))]
|
||||
}
|
||||
|
||||
@objc private func closeThis() {
|
||||
searchBar.endEditing(true)
|
||||
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 {
|
||||
dataSourceLocal.count > 0 ? 2 : 1
|
||||
}
|
||||
|
||||
override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
switch section {
|
||||
case 0: return max(1, dataSource.count) + (searchActive ? 1 : 0)
|
||||
case 1: return dataSourceLocal.count
|
||||
default: preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
switch section {
|
||||
case 0: return "AppStore"
|
||||
case 1: return "Found in other recordings"
|
||||
default: preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "AppStoreSearchCell")!
|
||||
let bundleId: String
|
||||
let altLoadUrl: String?
|
||||
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
guard dataSource.count > 0, indexPath.row < dataSource.count else {
|
||||
if indexPath.row == 0 {
|
||||
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 …"
|
||||
}
|
||||
cell.detailTextLabel?.text = nil
|
||||
cell.imageView?.image = nil
|
||||
return cell
|
||||
}
|
||||
let src = dataSource[indexPath.row]
|
||||
bundleId = src.bundleId
|
||||
altLoadUrl = src.imageURL
|
||||
cell.textLabel?.text = src.name
|
||||
cell.detailTextLabel?.text = src.developer
|
||||
case 1:
|
||||
let src = dataSourceLocal[indexPath.row]
|
||||
bundleId = src.bundleId
|
||||
altLoadUrl = nil
|
||||
cell.textLabel?.text = src.name
|
||||
cell.detailTextLabel?.text = src.author
|
||||
default:
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
let sno = searchNo
|
||||
cell.imageView?.image = BundleIcon.image(bundleId) {
|
||||
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
|
||||
cell.imageView?.layer.masksToBounds = true
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
guard indexPath.row < dataSource.count else {
|
||||
showManualEntryAlert()
|
||||
return
|
||||
}
|
||||
let src = dataSource[indexPath.row]
|
||||
delegate?.appSearch(didSelect: src.bundleId, appName: src.name, developer: src.developer)
|
||||
case 1:
|
||||
let src = dataSourceLocal[indexPath.row]
|
||||
delegate?.appSearch(didSelect: src.bundleId, appName: src.name, developer: src.author)
|
||||
default: preconditionFailure()
|
||||
}
|
||||
closeThis()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Search Bar Delegate
|
||||
|
||||
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
||||
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 {
|
||||
performSearch()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
setSource([], false)
|
||||
return
|
||||
}
|
||||
AppStoreSearch.search(term) { source, error in
|
||||
DispatchQueue.main.async {
|
||||
setSource(source ?? [], error != nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
||||
searchBar.endEditing(true)
|
||||
}
|
||||
|
||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
closeThis()
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,14 @@ class TVCPreviousRecords: UITableViewController, EditActionsRemove {
|
||||
let x = dataSource[indexPath.row]
|
||||
cell.textLabel?.text = x.title ?? x.fallbackTitle
|
||||
cell.textLabel?.textColor = (x.title == nil) ? .systemGray : nil
|
||||
cell.detailTextLabel?.text = "at \(DateFormat.seconds(x.start)), duration: \(TimeFormat.from(x.duration ?? 0))"
|
||||
cell.detailTextLabel?.text = "\(x.isShared ? "✅ " : "")at \(DateFormat.minutes(x.start)), duration: \(TimeFormat.from(x.duration))"
|
||||
cell.imageView?.image = x.isLongTerm ? nil : BundleIcon.image(x.appId)
|
||||
cell.imageView?.layer.cornerRadius = 6.75
|
||||
cell.imageView?.layer.masksToBounds = true
|
||||
if #available(iOS 11, *) {} else {
|
||||
cell.textLabel?.numberOfLines = 1
|
||||
cell.detailTextLabel?.numberOfLines = 1
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,16 @@ import UIKit
|
||||
|
||||
class TVCRecordingDetails: UITableViewController, EditActionsRemove {
|
||||
var record: Recording!
|
||||
private lazy var isLongRecording: Bool = (record.duration ?? 0) > Timestamp.hours(1)
|
||||
var noResults: Bool = false
|
||||
private lazy var isLongRecording: Bool = record.isLongTerm
|
||||
|
||||
private var showRaw: Bool = false
|
||||
/// Sorted by `ts` in ascending order (oldest first)
|
||||
private lazy var dataSourceRaw: [DomainTsPair] = RecordingsDB.details(record)
|
||||
private lazy var dataSourceRaw: [DomainTsPair] = {
|
||||
let list = RecordingsDB.details(record)
|
||||
noResults = list.count == 0
|
||||
return list
|
||||
}()
|
||||
/// Sorted by `count` (descending), then alphabetically
|
||||
private lazy var dataSourceSum: [(domain: String, count: Int)] = {
|
||||
var result: [String:Int] = [:]
|
||||
@@ -20,6 +25,14 @@ class TVCRecordingDetails: UITableViewController, EditActionsRemove {
|
||||
|
||||
override func viewDidLoad() {
|
||||
title = record.title ?? record.fallbackTitle
|
||||
NotifyRecordingChanged.observe(call: #selector(recordingDidChange(_:)), on: self)
|
||||
}
|
||||
|
||||
@objc private func recordingDidChange(_ notification: Notification) {
|
||||
let (rec, deleted) = notification.object as! (Recording, Bool)
|
||||
if rec.id == record.id, !deleted {
|
||||
record = rec // almost exclusively when 'shared' is set true
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction private func toggleDisplayStyle(_ sender: UIBarButtonItem) {
|
||||
@@ -28,16 +41,25 @@ class TVCRecordingDetails: UITableViewController, EditActionsRemove {
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if let tgt = segue.destination as? TVCShareRecording {
|
||||
tgt.record = self.record
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
|
||||
showRaw ? dataSourceRaw.count : dataSourceSum.count
|
||||
max(1, showRaw ? dataSourceRaw.count : dataSourceSum.count)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell: UITableViewCell
|
||||
if showRaw {
|
||||
if noResults {
|
||||
cell = tableView.dequeueReusableCell(withIdentifier: "RecordNoResultsCell")!
|
||||
cell.textLabel?.text = "– empty recording –"
|
||||
} else if showRaw {
|
||||
let x = dataSourceRaw[indexPath.row]
|
||||
if isLongRecording {
|
||||
cell = tableView.dequeueReusableCell(withIdentifier: "RecordDetailLongCell")!
|
||||
@@ -61,11 +83,11 @@ class TVCRecordingDetails: UITableViewController, EditActionsRemove {
|
||||
// MARK: - Editing
|
||||
|
||||
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
|
||||
getRowActionsIOS9(indexPath, tableView)
|
||||
noResults ? nil : getRowActionsIOS9(indexPath, tableView)
|
||||
}
|
||||
@available(iOS 11.0, *)
|
||||
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
getRowActionsIOS11(indexPath)
|
||||
noResults ? nil : getRowActionsIOS11(indexPath)
|
||||
}
|
||||
|
||||
func editableRowCallback(_ index: IndexPath, _ action: RowAction, _ userInfo: Any?) -> Bool {
|
||||
@@ -89,6 +111,57 @@ class TVCRecordingDetails: UITableViewController, EditActionsRemove {
|
||||
tableView.deleteRows(at: [index], with: .automatic)
|
||||
}
|
||||
}
|
||||
noResults = dataSourceRaw.count == 0
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Tap to Copy
|
||||
|
||||
private var cellMenu = TableCellTapMenu()
|
||||
private var copyDomain: String? = nil
|
||||
|
||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
if noResults { return nil }
|
||||
let buttons = [
|
||||
UIMenuItem(title: "All requests", action: #selector(openInLogs)),
|
||||
UIMenuItem(title: "Co-Occurrence", action: #selector(openCoOccurrence))
|
||||
]
|
||||
if cellMenu.start(tableView, indexPath, items: buttons) {
|
||||
if showRaw {
|
||||
copyDomain = cellMenu.getSelected(dataSourceRaw)?.domain
|
||||
} else {
|
||||
copyDomain = cellMenu.getSelected(dataSourceSum)?.domain
|
||||
}
|
||||
self.becomeFirstResponder()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override var canBecomeFirstResponder: Bool { true }
|
||||
|
||||
override func copy(_ sender: Any?) {
|
||||
if let dom = copyDomain {
|
||||
UIPasteboard.general.string = dom
|
||||
}
|
||||
cellMenu.reset()
|
||||
copyDomain = nil
|
||||
}
|
||||
|
||||
@objc private func openInLogs() {
|
||||
if let dom = copyDomain, let req = (tabBarController as? TBCMain)?.openTab(0) as? TVCDomains {
|
||||
VCDateFilter.disableFilter()
|
||||
req.pushOpen(domain: dom)
|
||||
}
|
||||
cellMenu.reset()
|
||||
copyDomain = nil
|
||||
}
|
||||
|
||||
@objc private func openCoOccurrence() {
|
||||
if let dom = copyDomain {
|
||||
present(VCCoOccurrence.make(dom), animated: true)
|
||||
}
|
||||
cellMenu.reset()
|
||||
copyDomain = nil
|
||||
}
|
||||
}
|
||||
|
||||
277
main/Recordings/TVCShareRecording.swift
Normal file
@@ -0,0 +1,277 @@
|
||||
import UIKit
|
||||
|
||||
class TVCShareRecording : UITableViewController, UITextViewDelegate, VCEditTextDelegate {
|
||||
|
||||
@IBOutlet private var sendButton: UIBarButtonItem!
|
||||
|
||||
// vars
|
||||
var record: Recording!
|
||||
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 = {
|
||||
let comp = Calendar.current.dateComponents(
|
||||
[.weekOfYear, .yearForWeekOfYear], from: Date(self.record.start))
|
||||
return "\(comp.yearForWeekOfYear ?? 0).\(comp.weekOfYear ?? 0)"
|
||||
}()
|
||||
|
||||
// Data source
|
||||
private lazy var dataSource: [String : [Timestamp]] = RecordingsDB.detailCluster(self.record)
|
||||
|
||||
private lazy var dataSourceKeyValue: [(key: String, value: String)] = [
|
||||
("Date", self.weekInYear),
|
||||
("Rec-Length", "\(self.record.duration) sec"),
|
||||
("App-Bundle", self.record.appId ?? " – "),
|
||||
("App-Name", self.record.title ?? " – "),
|
||||
("Notes", " – ") // see delegate below
|
||||
]
|
||||
|
||||
private lazy var dataSourceLogs: [(domain: String, occurrences: String, enabled: Bool)] = self.dataSource.map {
|
||||
($0.key, $0.value.map{"\($0)"}.joined(separator: ", "), true)
|
||||
}.sorted(by: { $0.domain < $1.domain })
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
if record.isShared {
|
||||
sendButton.tintColor = .gray
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadNotes() {
|
||||
tableView.reloadRows(at: [
|
||||
IndexPath(row: 0, section: 1), // edit field
|
||||
IndexPath(row: 4, section: 2) // display field
|
||||
], with: .automatic)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - User Interaction
|
||||
|
||||
@IBAction private func didChangeNotesCheckbox(_ sender: UISwitch) {
|
||||
shareNotes = sender.isOn
|
||||
reloadNotes()
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if let dest = segue.destination as? VCEditText {
|
||||
dest.text = editedNotes
|
||||
dest.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
func editText(didFinish text: String) {
|
||||
editedNotes = text
|
||||
reloadNotes()
|
||||
}
|
||||
|
||||
@IBAction private func shareRecording(_ sender: UIBarButtonItem) {
|
||||
guard !record.isShared else {
|
||||
showAlertAlreadyShared()
|
||||
return
|
||||
}
|
||||
navigationItem.rightBarButtonItem = {
|
||||
let v = UIView()
|
||||
let activity = UIActivityIndicatorView()
|
||||
v.addSubview(activity)
|
||||
activity.anchor([.centerX, .centerY], to: v)
|
||||
activity.startAnimating()
|
||||
v.widthAnchor =&= 2 * activity.widthAnchor
|
||||
return UIBarButtonItem(customView: v)
|
||||
}()
|
||||
|
||||
postToServer() { [weak self] in
|
||||
self?.navigationItem.rightBarButtonItem = self?.sendButton
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
override func numberOfSections(in _: UITableView) -> Int { 4 }
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
switch section {
|
||||
case 0: return 1 // description
|
||||
case 1: return hasNotes ? 2 : 0 // notes + checkbox
|
||||
case 2: return dataSourceKeyValue.count
|
||||
case 3: return dataSourceLogs.count
|
||||
default: preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
switch section {
|
||||
case 0: return "Review before sending"
|
||||
case 1: return hasNotes ? "Notes" : nil
|
||||
case 2: return "Send to server"
|
||||
case 3: return "Logs"
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
switch section {
|
||||
case 0: return "You can tap on a domain cell to exclude it from the upload."
|
||||
case 2: return "Below you see the domain names, followed by a list of relative time offsets (in seconds)."
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell: UITableViewCell
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
cell = tableView.dequeueReusableCell(withIdentifier: "shareTextCell")!
|
||||
cell.textLabel?.text = """
|
||||
You are about to upload the following information to our servers.
|
||||
The data is anonymized in regards to device identifiers and time of recording. However, it is not anonymous to the domains requested during the recording.
|
||||
"""
|
||||
case 1:
|
||||
switch indexPath.row {
|
||||
case 0:
|
||||
cell = tableView.dequeueReusableCell(withIdentifier: "shareOpenTextCell")!
|
||||
cell.textLabel?.text = editedNotes
|
||||
cell.textLabel?.textColor = shareNotes ? nil : .gray
|
||||
case 1:
|
||||
cell = tableView.dequeueReusableCell(withIdentifier: "shareCheckboxCell")!
|
||||
cell.textLabel?.text = "Upload your notes?"
|
||||
let accessory = cell.accessoryView as! UISwitch
|
||||
accessory.isOn = shareNotes
|
||||
default: preconditionFailure()
|
||||
}
|
||||
case 2:
|
||||
cell = tableView.dequeueReusableCell(withIdentifier: "shareKeyValueCell")!
|
||||
let src = dataSourceKeyValue[indexPath.row]
|
||||
cell.textLabel?.text = src.key
|
||||
let flag = indexPath.row == 4 && shareNotes && hasNotes
|
||||
cell.detailTextLabel?.text = flag ? editedNotes : src.value
|
||||
case 3:
|
||||
cell = tableView.dequeueReusableCell(withIdentifier: "shareLogCell")!
|
||||
let src = dataSourceLogs[indexPath.row]
|
||||
let sent = src.enabled
|
||||
cell.textLabel?.text = src.domain
|
||||
cell.detailTextLabel?.text = sent ? src.occurrences : "don't upload"
|
||||
cell.accessoryType = sent ? .checkmark : .none
|
||||
cell.textLabel?.isEnabled = sent
|
||||
cell.detailTextLabel?.isEnabled = sent
|
||||
default:
|
||||
preconditionFailure()
|
||||
}
|
||||
if #available(iOS 11, *) {} else {
|
||||
cell.detailTextLabel?.numberOfLines = 1
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard indexPath.section == 3 else { return }
|
||||
dataSourceLogs[indexPath.row].enabled = !dataSourceLogs[indexPath.row].enabled
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Upload
|
||||
|
||||
private func postToServer(_ onceLoaded: @escaping () -> Void) {
|
||||
// prepare json
|
||||
let allowed = dataSourceLogs.filter{ $0.enabled }.map{ $0.domain }
|
||||
let json = try? JSONSerialization.data(withJSONObject: [
|
||||
"v" : 1,
|
||||
"date" : weekInYear,
|
||||
"duration" : record.duration,
|
||||
"app-bundle" : record.appId ?? "",
|
||||
"app-name" : record.title ?? "",
|
||||
"notes" : shareNotes ? editedNotes : "",
|
||||
"logs" : dataSource.filter{ allowed.contains($0.key) }
|
||||
])
|
||||
|
||||
// prepare post request
|
||||
let url = URL(string: "https://appchk.de/api/v1/contribute/")!
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = json
|
||||
var rec = record! // store temporarily so self can be released
|
||||
|
||||
// send to server
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
onceLoaded()
|
||||
guard error == nil, let data = data,
|
||||
let response = response as? HTTPURLResponse else {
|
||||
self?.banner(.fail, "\(error?.localizedDescription ?? "Unkown error occurred")")
|
||||
return
|
||||
}
|
||||
guard let json = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any],
|
||||
let v = json["v"] as? Int, v > 0 else {
|
||||
QLog.Warning("Couldn't contribute: Not JSON or no version key")
|
||||
self?.banner(.fail, "Server couldn't parse request.\nTry again later.")
|
||||
return
|
||||
}
|
||||
let status = json["status"] as? String ?? "unkown reason"
|
||||
guard status == "ok", (200 ... 299) ~= response.statusCode else {
|
||||
QLog.Warning("Couldn't contribute: \(status)")
|
||||
self?.banner(.fail, "Error: \(status)")
|
||||
return
|
||||
}
|
||||
// update db, mark record as shared
|
||||
rec.uploadkey = json["key"] as? String ?? "_"
|
||||
self?.record = rec // in case view is still open
|
||||
RecordingsDB.update(rec) // rec cause self may not be available
|
||||
self?.sendButton.tintColor = .gray
|
||||
// notify user about results
|
||||
if v == 1, let urlStr = json["url"] as? String {
|
||||
let nextUpdateIn = json["when"] as? Int
|
||||
self?.showAlertAvailableSoon(urlStr, when: nextUpdateIn)
|
||||
}
|
||||
self?.banner(.ok, "Thank you for your contribution.")
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Alerts & Banner
|
||||
|
||||
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?) {
|
||||
var msg = "Your contribution is being processed and will be available "
|
||||
if let when = when {
|
||||
if when < 61 {
|
||||
msg += "in approx. \(when) sec. "
|
||||
} else {
|
||||
let fmt = TimeFormat.from(Timestamp(when))
|
||||
msg += "in \(fmt) min. "
|
||||
}
|
||||
} else {
|
||||
msg += "shortly. "
|
||||
}
|
||||
msg += "Open results webpage now?"
|
||||
AskAlert(title: "Thank you", text: msg, buttonText: "Show results", cancelButton: "Not now") { _ in
|
||||
if let url = URL(string: urlStr) {
|
||||
UIApplication.shared.openURL(url)
|
||||
}
|
||||
}.presentIn(self)
|
||||
}
|
||||
|
||||
private func showAlertAlreadyShared() {
|
||||
let alert = Alert(title: nil, text: "You already shared this recording.")
|
||||
if let bid = record.appId, bid.isValidBundleId() {
|
||||
alert.addAction(UIAlertAction.init(title: "Open results", style: .default, handler: { _ in
|
||||
URL(string: "https://appchk.de/redirect.html?id=\(bid)")?.open()
|
||||
}))
|
||||
}
|
||||
alert.presentIn(self)
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,97 @@
|
||||
import UIKit
|
||||
|
||||
class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate {
|
||||
class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate, TVCAppSearchDelegate {
|
||||
|
||||
var record: Recording!
|
||||
var deleteOnCancel: Bool = false
|
||||
var appId: String?
|
||||
|
||||
@IBOutlet private var buttonCancel: UIBarButtonItem!
|
||||
@IBOutlet private var buttonSave: UIBarButtonItem!
|
||||
@IBOutlet private var inputTitle: UITextField!
|
||||
@IBOutlet private var appTitle: UILabel!
|
||||
@IBOutlet private var appDeveloper: UILabel!
|
||||
@IBOutlet private var appIcon: UIImageView!
|
||||
@IBOutlet private var inputNotes: UITextView!
|
||||
@IBOutlet private var inputDetails: UITextView!
|
||||
@IBOutlet private var noteBottom: NSLayoutConstraint!
|
||||
|
||||
@IBOutlet private var chooseAppTap: UITapGestureRecognizer!
|
||||
@IBOutlet private var buttonFilter: UIButton!
|
||||
|
||||
override func viewDidLoad() {
|
||||
inputTitle.placeholder = record.fallbackTitle
|
||||
inputTitle.text = record.title
|
||||
inputNotes.text = record.notes
|
||||
inputDetails.text = """
|
||||
Start: \(DateFormat.seconds(record.start))
|
||||
End: \(record.stop == nil ? "?" : DateFormat.seconds(record.stop!))
|
||||
Duration: \(TimeFormat.from(record.duration ?? 0))
|
||||
"""
|
||||
validateSaveButton()
|
||||
if deleteOnCancel { // mark as destructive
|
||||
if deleteOnCancel { // aka newly created
|
||||
let r = record!
|
||||
DispatchQueue.global().async {
|
||||
RecordingsDB.persist(r)
|
||||
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
|
||||
appTitle.text = record.fallbackTitle
|
||||
appDeveloper.text = nil
|
||||
chooseAppTap.isEnabled = false
|
||||
} else {
|
||||
appId = record.appId
|
||||
appIcon.image = BundleIcon.image(record.appId)
|
||||
appIcon.layer.cornerRadius = 6.75
|
||||
appIcon.layer.masksToBounds = true
|
||||
if record.appId == nil {
|
||||
appTitle.text = "Tap here to choose app"
|
||||
appDeveloper.text = record.title
|
||||
} else {
|
||||
appTitle.text = record.title ?? record.fallbackTitle
|
||||
appDeveloper.text = record.subtitle
|
||||
}
|
||||
}
|
||||
inputNotes.text = record.notes
|
||||
inputDetails.text = """
|
||||
Start: \(DateFormat.seconds(record.start))
|
||||
End: \(record.stop == nil ? "?" : DateFormat.seconds(record.stop!))
|
||||
Duration: \(TimeFormat.from(record.duration))
|
||||
"""
|
||||
validateSaveButton()
|
||||
UIResponder.keyboardWillShowNotification.observe(call: #selector(keyboardWillShow), on: self)
|
||||
UIResponder.keyboardWillHideNotification.observe(call: #selector(keyboardWillHide), on: self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Save & Cancel Buttons
|
||||
|
||||
@IBAction func didTapSave(_ sender: UIBarButtonItem) {
|
||||
let newlyCreated = deleteOnCancel
|
||||
if newlyCreated {
|
||||
// if remains true, `viewDidDisappear` will delete the record
|
||||
deleteOnCancel = false
|
||||
}
|
||||
QLog.Debug("updating record #\(record.id)")
|
||||
record.title = (inputTitle.text == "") ? nil : inputTitle.text
|
||||
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)
|
||||
}
|
||||
}
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if let tvc = segue.destination as? TVCAppSearch {
|
||||
tvc.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func didTapCancel(_ sender: UIBarButtonItem) {
|
||||
// MARK: Save & Cancel Buttons
|
||||
|
||||
@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
|
||||
record.title = (appTitle.text == "") ? nil : appTitle.text
|
||||
record.subtitle = (appDeveloper.text == "") ? nil : appDeveloper.text
|
||||
} else {
|
||||
record.appId = nil
|
||||
record.title = nil
|
||||
record.subtitle = nil
|
||||
}
|
||||
record.notes = (inputNotes.text == "") ? nil : inputNotes.text
|
||||
dismiss(animated: true) {
|
||||
RecordingsDB.update(self.record)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func didTapCancel() {
|
||||
QLog.Debug("discard edit of record #\(record.id)")
|
||||
dismiss(animated: true)
|
||||
}
|
||||
@@ -67,6 +104,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
|
||||
|
||||
@@ -121,11 +168,18 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate
|
||||
func textViewDidChange(_ _: UITextView) { validateSaveButton() }
|
||||
|
||||
private func validateSaveButton() {
|
||||
let changed = (inputTitle.text != record.title ?? "" || inputNotes.text != record.notes ?? "")
|
||||
let changed = (appId != record.appId
|
||||
|| (appTitle.text != record.title && appTitle.text != "Tap here to choose app" && appTitle.text != record.fallbackTitle)
|
||||
|| appDeveloper.text != record.subtitle
|
||||
|| inputNotes.text != record.notes ?? "")
|
||||
buttonSave.isEnabled = changed || deleteOnCancel // always allow save for new recordings
|
||||
}
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
textField == inputTitle ? inputNotes.becomeFirstResponder() : true
|
||||
func appSearch(didSelect bundleId: String, appName: String?, developer: String?) {
|
||||
appId = bundleId
|
||||
appTitle.text = appName
|
||||
appDeveloper.text = developer
|
||||
appIcon.image = BundleIcon.image(bundleId)
|
||||
validateSaveButton()
|
||||
}
|
||||
}
|
||||
|
||||
39
main/Recordings/VCEditText.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
import UIKit
|
||||
|
||||
protocol VCEditTextDelegate {
|
||||
func editText(didFinish text: String)
|
||||
}
|
||||
|
||||
class VCEditText: UIViewController, UITextViewDelegate {
|
||||
|
||||
var text: String!
|
||||
var delegate: VCEditTextDelegate!
|
||||
|
||||
@IBOutlet private var textView: UITextView!
|
||||
@IBOutlet private var textBottom: NSLayoutConstraint!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
textView.text = text
|
||||
textView.becomeFirstResponder()
|
||||
|
||||
UIResponder.keyboardWillShowNotification.observe(call: #selector(keyboardWillShow), on: self)
|
||||
UIResponder.keyboardWillHideNotification.observe(call: #selector(keyboardWillHide), on: self)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
delegate.editText(didFinish: textView.text)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Adapt to Keyboard
|
||||
|
||||
@objc func keyboardWillShow(_ notification: NSNotification) {
|
||||
textBottom.constant = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height ?? 0
|
||||
}
|
||||
|
||||
@objc func keyboardWillHide(_ notification: NSNotification) {
|
||||
textBottom.constant = 0
|
||||
}
|
||||
}
|
||||
@@ -3,138 +3,144 @@ import UIKit
|
||||
class VCRecordings: UIViewController, UINavigationControllerDelegate {
|
||||
private var currentRecording: Recording?
|
||||
private var recordingTimer: Timer?
|
||||
private var state: CurrentRecordingState = .Off
|
||||
|
||||
@IBOutlet private var headerView: UIView!
|
||||
@IBOutlet private var buttonView: UIView!
|
||||
@IBOutlet private var runningView: UIView!
|
||||
@IBOutlet private var timeLabel: UILabel!
|
||||
@IBOutlet private var startButton: UIButton!
|
||||
@IBOutlet private var startNewRecView: UIView!
|
||||
private var prevRecController: UINavigationController!
|
||||
@IBOutlet private var stopButton: UIButton!
|
||||
@IBOutlet private var startSegment: UISegmentedControl!
|
||||
|
||||
override func viewDidLoad() {
|
||||
prevRecController = (children.first as! UINavigationController)
|
||||
prevRecController.delegate = self
|
||||
startSegment.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.sysLink], for: .normal)
|
||||
timeLabel.font = timeLabel.font.monoSpace()
|
||||
// hide timer if not running
|
||||
updateUI(setRecording: false, animated: false)
|
||||
currentRecording = RecordingsDB.getCurrent()
|
||||
|
||||
if let ongoing = RecordingsDB.getCurrent() {
|
||||
currentRecording = ongoing
|
||||
// Currently this class is the only one that changes the state,
|
||||
// if that ever changes, make sure to update local state as well
|
||||
state = PrefsShared.CurrentlyRecording
|
||||
startTimer(animate: false, longterm: state == .Background)
|
||||
} else { // hide timer if not running
|
||||
updateUI(setRecording: false, animated: false)
|
||||
}
|
||||
if !Prefs.DidShowTutorial.Recordings {
|
||||
self.perform(#selector(showTutorial), with: nil, afterDelay: 0.5)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
let x = TutorialSheet()
|
||||
x.addSheet().addArrangedSubview(TinyMarkdown.load("tut-recording-1"))
|
||||
x.addSheet().addArrangedSubview(TinyMarkdown.load("tut-recording-2"))
|
||||
x.buttonTitleDone = "Got it"
|
||||
x.present {
|
||||
Prefs.DidShowTutorial.Recordings = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
if currentRecording != nil { startTimer(animate: false) }
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
recordingTimer?.fireDate = .distantPast
|
||||
navigationController?.setNavigationBarHidden(true, animated: animated)
|
||||
// set hidden in will appear causes UITableViewAlertForLayoutOutsideViewHierarchy
|
||||
// but otherwise navBar is visible during transition
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
stopTimer(animate: false)
|
||||
super.viewWillDisappear(animated)
|
||||
recordingTimer?.fireDate = .distantFuture
|
||||
navigationController?.setNavigationBarHidden(false, animated: animated)
|
||||
}
|
||||
|
||||
func navigationController(_ nav: UINavigationController, willShow vc: UIViewController, animated: Bool) {
|
||||
hideNewRecording(isRootVC: (vc == nav.viewControllers.first), didShow: false)
|
||||
}
|
||||
|
||||
func navigationController(_ nav: UINavigationController, didShow vc: UIViewController, animated: Bool) {
|
||||
// TODO: use interactive animation handler to dynamically animate "new recording" view
|
||||
hideNewRecording(isRootVC: (vc == nav.viewControllers.first), didShow: true)
|
||||
}
|
||||
|
||||
private func hideNewRecording(isRootVC: Bool, didShow: Bool) {
|
||||
if isRootVC == didShow {
|
||||
UIView.animate(withDuration: 0.3) {
|
||||
self.startNewRecView.isHidden = !isRootVC // hide "new recording" if details open
|
||||
}
|
||||
@IBAction private func showInfo(_ sender: UIButton?) {
|
||||
let x = TutorialSheet()
|
||||
x.addSheet().addArrangedSubview(TinyMarkdown.load("tut-recording-howto"))
|
||||
x.buttonTitleDone = "Close"
|
||||
x.present() {
|
||||
Prefs.DidShowTutorial.RecordingHowTo = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Start New Recording
|
||||
|
||||
@IBAction private func startRecordingButtonTapped(_ sender: UIButton) {
|
||||
if recordingTimer == nil {
|
||||
currentRecording = RecordingsDB.startNew()
|
||||
startTimer(animate: true)
|
||||
} else {
|
||||
stopTimer(animate: true)
|
||||
RecordingsDB.stop(¤tRecording!)
|
||||
prevRecController.popToRootViewController(animated: true)
|
||||
let editVC = (prevRecController.topViewController as! TVCPreviousRecords)
|
||||
editVC.insertAndEditRecording(currentRecording!)
|
||||
currentRecording = nil // otherwise it will restart
|
||||
}
|
||||
}
|
||||
|
||||
private func startTimer(animate: Bool) {
|
||||
guard let r = currentRecording, r.stop == nil else {
|
||||
@IBAction private func startRecording(_ sender: UISegmentedControl) {
|
||||
guard GlassVPN.state == .on else {
|
||||
AskAlert(title: "VPN stopped",
|
||||
text: "You need to start the VPN proxy before you can start a recording.",
|
||||
buttonText: "Start") { _ in
|
||||
GlassVPN.setEnabled(true)
|
||||
}.presentIn(self)
|
||||
return
|
||||
}
|
||||
recordingTimer = Timer.repeating(0.086, call: #selector(timerCallback(_:)), on: self, userInfo: Date(r.start))
|
||||
updateUI(setRecording: true, animated: animate)
|
||||
guard Prefs.DidShowTutorial.RecordingHowTo else {
|
||||
showInfo(nil) // show at least once. Later, user can click the help icon.
|
||||
return
|
||||
}
|
||||
currentRecording = RecordingsDB.startNew()
|
||||
QLog.Debug("start recording #\(currentRecording!.id)")
|
||||
let longterm = sender.selectedSegmentIndex == 1
|
||||
startTimer(animate: true, longterm: longterm)
|
||||
notifyVPN(setRecording: longterm ? .Background : .App)
|
||||
}
|
||||
|
||||
@objc private func timerCallback(_ sender: Timer) {
|
||||
timeLabel.text = TimeFormat.since(sender.userInfo as! Date, millis: true)
|
||||
@IBAction private func stopRecording(_ sender: UIButton) {
|
||||
let validRecording = (state == .Background) == currentRecording!.isLongTerm
|
||||
notifyVPN(setRecording: .Off) // will change state = .Off
|
||||
stopTimer()
|
||||
QLog.Debug("stop recording #\(currentRecording!.id)")
|
||||
RecordingsDB.stop(¤tRecording!)
|
||||
if validRecording {
|
||||
let editVC = (children.first as! TVCPreviousRecords)
|
||||
editVC.insertAndEditRecording(currentRecording!)
|
||||
} else {
|
||||
QLog.Debug("Discard illegal recording #\(currentRecording!.id)")
|
||||
RecordingsDB.delete(currentRecording!)
|
||||
}
|
||||
currentRecording = nil // otherwise it will restart
|
||||
}
|
||||
|
||||
private func stopTimer(animate: Bool) {
|
||||
recordingTimer?.invalidate()
|
||||
recordingTimer = nil
|
||||
updateUI(setRecording: false, animated: animate)
|
||||
private func notifyVPN(setRecording state: CurrentRecordingState) {
|
||||
PrefsShared.CurrentlyRecording = state
|
||||
self.state = state
|
||||
GlassVPN.send(.isRecording(state))
|
||||
}
|
||||
|
||||
private func updateUI(setRecording: Bool, animated: Bool) {
|
||||
let title = setRecording ? "Stop Recording" : "Start New Recording"
|
||||
let color = setRecording ? UIColor.systemRed : nil
|
||||
let yT = setRecording ? 0 : -timeLabel.frame.height
|
||||
let yB = (setRecording ? 1 : 0.5) * (startButton.superview!.frame.height - startButton.frame.height)
|
||||
if !animated { // else title will flash
|
||||
startButton.titleLabel?.text = title
|
||||
}
|
||||
UIView.animate(withDuration: animated ? 0.3 : 0) {
|
||||
self.timeLabel.frame.origin.y = yT
|
||||
self.startButton.frame.origin.y = yB
|
||||
self.startButton.setTitle(title, for: .normal)
|
||||
self.startButton.setTitleColor(color, for: .normal)
|
||||
stopButton.tag = 99 // tag used in timerCallback()
|
||||
stopButton.setTitle("", for: .normal) // prevent flashing while animating in and out
|
||||
let block = {
|
||||
self.headerView.isHidden = setRecording
|
||||
self.buttonView.isHidden = setRecording
|
||||
self.runningView.isHidden = !setRecording
|
||||
}
|
||||
animated ? UIView.animate(withDuration: 0.3, animations: block) : block()
|
||||
}
|
||||
|
||||
private func startTimer(animate: Bool, longterm: Bool) {
|
||||
guard let r = currentRecording, r.stop == nil else {
|
||||
return
|
||||
}
|
||||
updateUI(setRecording: true, animated: animate)
|
||||
let freq = longterm ? 1 : 0.086
|
||||
let obj = (longterm, Date(r.start))
|
||||
recordingTimer = Timer.repeating(freq, call: #selector(timerCallback(_:)), on: self, userInfo: obj)
|
||||
recordingTimer!.fire() // update label immediately
|
||||
}
|
||||
|
||||
// MARK: Tutorial View Controller
|
||||
private func stopTimer() {
|
||||
recordingTimer?.invalidate()
|
||||
recordingTimer = nil
|
||||
updateUI(setRecording: false, animated: true)
|
||||
}
|
||||
|
||||
@objc private func showTutorial() {
|
||||
let x = TutorialSheet()
|
||||
x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString()
|
||||
.h1("What are Recordings?\n")
|
||||
.normal("\nSimilar to the default logging, recordings will intercept every request and log it for later review. " +
|
||||
"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 for background traffic." +
|
||||
"\n\n" +
|
||||
"Optionally, you can help us by providing 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.")
|
||||
))
|
||||
x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString()
|
||||
.h1("How to record?\n")
|
||||
.normal("\nBefore you begin a new recording make sure that you quit all running applications. " +
|
||||
"Tap on the 'Start Recording' button and switch to the application you'd like to inspect. " +
|
||||
"Use the App as you would normally. Try to get to all corners and functionality the App provides. " +
|
||||
"When you feel that you have captured enough content, come back to ").italic("AppCheck").normal(" and stop the recording." +
|
||||
"\n\n" +
|
||||
"Upon completion you will find your recording in the 'Previous Recordings' section. " +
|
||||
"You can review your results and remove user specific information if necessary.")
|
||||
))
|
||||
x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString()
|
||||
.h1("Share results\n")
|
||||
.normal("\nThis step is completely ").bold("optional").normal(". " +
|
||||
"You can choose to share your results with us. " +
|
||||
"We can compare similar applications and suggest privacy friendly alternatives. " +
|
||||
"Together with other likeminded individuals we can increase the awareness for privacy friendly design." +
|
||||
"\n\n" +
|
||||
"Thank you very much.")
|
||||
))
|
||||
x.buttonTitleDone = "Got it"
|
||||
x.present {
|
||||
Prefs.DidShowTutorial.Recordings = true
|
||||
@objc private func timerCallback(_ sender: Timer) {
|
||||
let (slow, start) = sender.userInfo as! (Bool, Date)
|
||||
timeLabel.text = TimeFormat.since(start, millis: !slow, hours: slow)
|
||||
let valid = slow == currentRecording!.isLongTerm
|
||||
let validInt = (valid ? 1 : 0)
|
||||
if stopButton.tag != validInt {
|
||||
stopButton.tag = validInt
|
||||
stopButton.setTitle(valid ? "Stop" : slow ? "Cancel" : "Discard", for: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,13 +48,10 @@ class VCAnalysisBar: UIViewController, UITabBarDelegate {
|
||||
}
|
||||
|
||||
private func openCoOccurrence() {
|
||||
guard let delegate = parent as? AnalysisBarDelegate,
|
||||
let vc: VCCoOccurrence = storyboard?.load("IBCoOccurrence") else {
|
||||
guard let delegate = parent as? AnalysisBarDelegate else {
|
||||
return
|
||||
}
|
||||
let info = delegate.analysisBarWillOpenCoOccurrence()
|
||||
vc.domainName = info.domain
|
||||
vc.isFQDN = info.isFQDN
|
||||
present(vc, animated: true)
|
||||
let x = delegate.analysisBarWillOpenCoOccurrence()
|
||||
present(VCCoOccurrence.make(x.domain, isFQDN: x.isFQDN), animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,14 @@ class VCCoOccurrence: UIViewController, UITableViewDataSource {
|
||||
private var logTimeDelta: CGFloat = 1
|
||||
private var logMaxCount: CGFloat = 1
|
||||
|
||||
static func make(_ domain: String, isFQDN: Bool = true) -> Self {
|
||||
let story = UIStoryboard(name: "CoOccurrence", bundle: nil)
|
||||
let vc = story.instantiateInitialViewController() as! Self
|
||||
vc.domainName = domain
|
||||
vc.isFQDN = isFQDN
|
||||
return vc
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
selectedTime = Prefs.ContextAnalyis.CoOccurrenceTime // calls `didSet` and `logTimeDelta`
|
||||
@@ -132,21 +140,9 @@ extension VCCoOccurrence {
|
||||
}()
|
||||
|
||||
let x = TutorialSheet()
|
||||
x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString()
|
||||
.h3("Co-Occurrence")
|
||||
.normal(" allows you to find requests that happen often at the same time as the selected domain. " +
|
||||
"Hence it will give you a hint what Apps might be involved in the activity." +
|
||||
"\n\nHow do you interpret these results? Lets look at an example:\n\n")
|
||||
.centered(.image(sampleCell))
|
||||
.normal("\n\nThe domain ").bold("example.org").normal(" had ").bold("14").normal(" requests with an ").italic("average time divergence").normal(" of ").bold("0.71 seconds").normal(". " +
|
||||
"That is, these 14 domain calls happend, on average, less then a second before or after the original request of the selected domain." +
|
||||
"\n\nClose temporal proximity and high occurrence counts are both indicators for domain correlation. " +
|
||||
"Results are sorted by a ranking index (").bold("9.").normal(") which strikes a balance between the two. " +
|
||||
"Preferring entries with higher counts as well as low time divergence.")
|
||||
.italic("\n\nTip: ").normal("As a visual guide you can look for the colored bar beside each value. " +
|
||||
"The larger the bar, the greater the correlation.")
|
||||
))
|
||||
|
||||
x.addSheet().addArrangedSubview(TinyMarkdown.load("tut-cooccurrence", replacements: [
|
||||
"<IMG>" : .init(image: sampleCell, centered: true)
|
||||
]))
|
||||
x.present(in: self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,32 +66,24 @@ class TVCOccurrenceContext: UITableViewController {
|
||||
|
||||
// MARK: - Tap to Copy
|
||||
|
||||
private var rowToCopy: Int = Int.max
|
||||
|
||||
private var cellMenu = TableCellTapMenu()
|
||||
private var copyDomain: String? = nil
|
||||
|
||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
if firstOrLast(indexPath.row) { return nil }
|
||||
if rowToCopy == indexPath.row {
|
||||
UIMenuController.shared.setMenuVisible(false, animated: true)
|
||||
rowToCopy = Int.max
|
||||
return nil
|
||||
if !firstOrLast(indexPath.row), cellMenu.start(tableView, indexPath) {
|
||||
copyDomain = cellMenu.getSelected(dataSource)?.domain
|
||||
self.becomeFirstResponder()
|
||||
}
|
||||
rowToCopy = indexPath.row
|
||||
self.becomeFirstResponder()
|
||||
let cell = tableView.cellForRow(at: indexPath)!
|
||||
UIMenuController.shared.setTargetRect(cell.bounds, in: cell)
|
||||
UIMenuController.shared.setMenuVisible(true, animated: true)
|
||||
return nil
|
||||
}
|
||||
|
||||
override var canBecomeFirstResponder: Bool { true }
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
action == #selector(UIResponderStandardEditActions.copy)
|
||||
}
|
||||
|
||||
override func copy(_ sender: Any?) {
|
||||
guard rowToCopy < dataSource.count else { return }
|
||||
UIPasteboard.general.string = dataSource[rowToCopy].domain
|
||||
rowToCopy = Int.max
|
||||
if let dom = copyDomain {
|
||||
UIPasteboard.general.string = dom
|
||||
}
|
||||
cellMenu.reset()
|
||||
copyDomain = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,4 +116,20 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate {
|
||||
NotifyDateFilterChanged.post()
|
||||
}
|
||||
}
|
||||
|
||||
static func disableFilter() {
|
||||
if Prefs.DateFilter.Kind <-? .Off {
|
||||
Prefs.DateFilter.LastXMin = 0
|
||||
Prefs.DateFilter.RangeA = nil
|
||||
Prefs.DateFilter.RangeB = nil
|
||||
NotifyDateFilterChanged.post()
|
||||
}
|
||||
}
|
||||
|
||||
static func setFilter(range from: Timestamp?, to: Timestamp?) {
|
||||
Prefs.DateFilter.Kind = .ABRange
|
||||
Prefs.DateFilter.RangeA = from
|
||||
Prefs.DateFilter.RangeB = to
|
||||
NotifyDateFilterChanged.post()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,6 @@ class TVCFilter: UITableViewController, EditActionsRemove {
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "DomainFilterCell")!
|
||||
cell.textLabel?.text = dataSource[indexPath.row]
|
||||
if cell.gestureRecognizers?.isEmpty ?? true {
|
||||
cell.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(didLongTap)))
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
@@ -75,29 +72,24 @@ class TVCFilter: UITableViewController, EditActionsRemove {
|
||||
|
||||
// MARK: - Long Press Gesture
|
||||
|
||||
private var cellTitleCopy: String?
|
||||
|
||||
@objc private func didLongTap(_ sender: UILongPressGestureRecognizer) {
|
||||
guard let cell = sender.view as? UITableViewCell else {
|
||||
return
|
||||
}
|
||||
if sender.state == .began {
|
||||
cellTitleCopy = cell.textLabel?.text
|
||||
private var cellMenu = TableCellTapMenu()
|
||||
private var copyDomain: String? = nil
|
||||
|
||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
if cellMenu.start(tableView, indexPath) {
|
||||
copyDomain = cellMenu.getSelected(dataSource)
|
||||
self.becomeFirstResponder()
|
||||
let menu = UIMenuController.shared
|
||||
// menu.setTargetRect(CGRect(origin: sender.location(in: cell), size: CGSize.zero), in: cell)
|
||||
menu.setTargetRect(cell.bounds, in: cell)
|
||||
menu.setMenuVisible(true, animated: true)
|
||||
}
|
||||
}
|
||||
override var canBecomeFirstResponder: Bool { get { true } }
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
action == #selector(UIResponderStandardEditActions.copy)
|
||||
return nil
|
||||
}
|
||||
|
||||
override var canBecomeFirstResponder: Bool { true }
|
||||
|
||||
override func copy(_ sender: Any?) {
|
||||
UIPasteboard.general.string = cellTitleCopy
|
||||
cellTitleCopy = nil
|
||||
if let dom = copyDomain {
|
||||
UIPasteboard.general.string = dom
|
||||
}
|
||||
cellMenu.reset()
|
||||
copyDomain = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import UIKit
|
||||
class TVCSettings: UITableViewController {
|
||||
|
||||
@IBOutlet var vpnToggle: UISwitch!
|
||||
@IBOutlet var unresolvableToggle: UISwitch!
|
||||
@IBOutlet var swcdToggle: UISwitch!
|
||||
@IBOutlet var cellDomainsIgnored: UITableViewCell!
|
||||
@IBOutlet var cellDomainsBlocked: UITableViewCell!
|
||||
@IBOutlet var cellPrivacyAutoDelete: UITableViewCell!
|
||||
@@ -14,6 +16,7 @@ class TVCSettings: UITableViewController {
|
||||
reloadVPNState()
|
||||
reloadLoggingFilterUI()
|
||||
reloadPrivacyUI()
|
||||
reloadAdvancedUI()
|
||||
NotifyVPNStateChanged.observe(call: #selector(reloadVPNState), on: self)
|
||||
NotifyDNSFilterChanged.observe(call: #selector(reloadLoggingFilterUI), on: self)
|
||||
}
|
||||
@@ -191,6 +194,21 @@ extension TVCSettings {
|
||||
// MARK: - Advanced
|
||||
|
||||
extension TVCSettings {
|
||||
private func reloadAdvancedUI() {
|
||||
unresolvableToggle.isOn = PrefsShared.ForceDisconnectUnresolvableDNS
|
||||
swcdToggle.isOn = PrefsShared.ForceDisconnectSWCD
|
||||
}
|
||||
|
||||
@IBAction private func togglePreventUnresolvable(_ sender: UISwitch) {
|
||||
PrefsShared.ForceDisconnectUnresolvableDNS = sender.isOn
|
||||
GlassVPN.send(.disconnectUnresolvable(sender.isOn))
|
||||
}
|
||||
|
||||
@IBAction private func togglePreventSWCD(_ sender: UISwitch) {
|
||||
PrefsShared.ForceDisconnectSWCD = sender.isOn
|
||||
GlassVPN.send(.disconnectSWCD(sender.isOn))
|
||||
}
|
||||
|
||||
@IBAction private func exportDB() {
|
||||
AppDB?.vacuum()
|
||||
let sheet = UIActivityViewController(activityItems: [URL.internalDB()], applicationActivities: nil)
|
||||
|
||||
@@ -34,27 +34,8 @@ class TBCMain: UITabBarController {
|
||||
|
||||
@objc private func showWelcomeMessage() {
|
||||
let x = TutorialSheet()
|
||||
x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString()
|
||||
.h1("Welcome\n")
|
||||
.normal("\nAppCheck helps you identify which applications communicate with third parties. " +
|
||||
"It does so by logging network requests. " +
|
||||
"AppCheck learns only the destination addresses, not the actual data that is exchanged." +
|
||||
"\n\n" +
|
||||
"Your data belongs to you. " +
|
||||
"Therefore, monitoring and analysis take place on your device only. " +
|
||||
"The app does not share any data with us or any other third-party. " +
|
||||
"Unless you choose to.")
|
||||
))
|
||||
x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString()
|
||||
.h1("How it works\n")
|
||||
.normal("\nAppCheck creates a local VPN tunnel to intercept all network connections. " +
|
||||
"For each connection AppCheck looks into the DNS headers only, namely the domain names. " +
|
||||
"\n" +
|
||||
"These domain names are logged in the background while the VPN is active. " +
|
||||
"That means, AppCheck does not have to be active in the foreground. " +
|
||||
"You can close the app and come back later to see the results."
|
||||
)
|
||||
))
|
||||
x.addSheet().addArrangedSubview(TinyMarkdown.load("tut-welcome-1"))
|
||||
x.addSheet().addArrangedSubview(TinyMarkdown.load("tut-welcome-2"))
|
||||
x.present {
|
||||
Prefs.DidShowTutorial.Welcome = true
|
||||
}
|
||||
@@ -62,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,129 +0,0 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
private let fm = FileManager.default
|
||||
private let documentsDir = try! fm.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||
private let bundleInfoDir = documentsDir.appendingPathComponent("bundleInfo", isDirectory:true)
|
||||
|
||||
|
||||
struct AppInfoType : Decodable {
|
||||
var id: String
|
||||
var name: String?
|
||||
var seller: String?
|
||||
var imageURL: URL?
|
||||
private var remoteImgURL: String?
|
||||
private var cache: Bool?
|
||||
private let localJSON: URL
|
||||
private let localImgURL: URL
|
||||
|
||||
static func initWorkingDir() {
|
||||
try? fm.createDirectory(at: bundleInfoDir, withIntermediateDirectories: true, attributes: nil)
|
||||
// print("init dir: \(bundleInfoDir)")
|
||||
}
|
||||
|
||||
init(id: String) {
|
||||
self.id = id
|
||||
if id == "" {
|
||||
name = "–?–"
|
||||
cache = true
|
||||
localJSON = URL(fileURLWithPath: "")
|
||||
localImgURL = localJSON
|
||||
} else {
|
||||
localJSON = bundleInfoDir.appendingPathComponent("\(id).json")
|
||||
localImgURL = bundleInfoDir.appendingPathComponent("\(id).img")
|
||||
reload()
|
||||
}
|
||||
}
|
||||
|
||||
mutating func reload() {
|
||||
if fm.fileExists(atPath: localImgURL.path) {
|
||||
imageURL = localImgURL
|
||||
}
|
||||
guard name == nil, seller == nil,
|
||||
fm.fileExists(atPath: localJSON.path),
|
||||
let attr = try? fm.attributesOfItem(atPath: localJSON.path),
|
||||
attr[FileAttributeKey.size] as! UInt64 > 0 else
|
||||
{
|
||||
// process json only if attributes not set yet,
|
||||
// OR json doesn't exist, OR json is empty
|
||||
return
|
||||
}
|
||||
(name, seller, remoteImgURL) = parseJSON(localJSON)
|
||||
|
||||
if remoteImgURL == nil || imageURL != nil {
|
||||
cache = true
|
||||
}
|
||||
}
|
||||
|
||||
func getImage() -> UIImage? {
|
||||
if let img = imageURL, let data = try? Data(contentsOf: img) {
|
||||
return UIImage(data: data, scale: 2.0)
|
||||
} else if id.hasPrefix("com.apple.") {
|
||||
return appIconApple
|
||||
} else {
|
||||
return appIconUnknown
|
||||
}
|
||||
}
|
||||
|
||||
private func parseJSON(_ location: URL) -> (name: String?, seller: String?, image: String?) {
|
||||
do {
|
||||
let data = try Data.init(contentsOf: location)
|
||||
if
|
||||
let json = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) as? [String: Any],
|
||||
let resAll = json["results"] as? [Any],
|
||||
let res = resAll.first as? [String: Any]
|
||||
{
|
||||
let name = res["trackName"] as? String // trackCensoredName
|
||||
let seller = res["sellerName"] as? String // artistName
|
||||
let image = res["artworkUrl60"] as? String // artworkUrl100
|
||||
return (name, seller, image)
|
||||
} else if id.hasPrefix("com.apple.") {
|
||||
return (String(id.dropFirst(10)), "Apple Inc.", nil)
|
||||
}
|
||||
} catch {}
|
||||
return (nil, nil, nil)
|
||||
}
|
||||
|
||||
mutating func updateIfNeeded(_ updateClosure: () -> Void) {
|
||||
guard cache == nil,
|
||||
let safeId = id.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
|
||||
return
|
||||
}
|
||||
cache = false // meaning: hasn't downloaded yet, but is about to do
|
||||
// print("downloading \(id)")
|
||||
_ = downloadURL("https://itunes.apple.com/lookup?bundleId=\(safeId)", toFile: localJSON).flatMap{
|
||||
// print("downloading \(id) done.")
|
||||
reload()
|
||||
updateClosure()
|
||||
return downloadURL(remoteImgURL, toFile: localImgURL)
|
||||
}.map{
|
||||
// print("downloading \(id) image done.")
|
||||
reload()
|
||||
updateClosure()
|
||||
}
|
||||
}
|
||||
|
||||
enum NetworkError: Error {
|
||||
case url
|
||||
}
|
||||
|
||||
private func downloadURL(_ urlStr: String?, toFile: URL) -> Result<Void, Error> {
|
||||
guard let urlStr = urlStr, let url = URL(string: urlStr) else {
|
||||
return .failure(NetworkError.url)
|
||||
}
|
||||
var result: Result<Void, Error>!
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
URLSession.shared.downloadTask(with: url) { location, response, error in
|
||||
if let loc = location {
|
||||
try? fm.removeItem(at: toFile)
|
||||
try? fm.moveItem(at: loc, to: toFile)
|
||||
result = .success(())
|
||||
} else {
|
||||
result = .failure(error!)
|
||||
}
|
||||
semaphore.signal()
|
||||
}.resume()
|
||||
_ = semaphore.wait(wallTimeout: .distantFuture)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import UIKit
|
||||
|
||||
let appIconApple = generateAppleIcon()
|
||||
let appIconUnknown = generateUnknownIcon()
|
||||
|
||||
func generateAppleIcon() -> UIImage? {
|
||||
let rect = CGRect(x: 0, y: 0, width: 30, height: 30)
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
|
||||
|
||||
// #colorLiteral(red: 0.5843137503, green: 0.8235294223, blue: 0.4196078479, alpha: 1).setFill()
|
||||
// UIBezierPath(roundedRect: rect, cornerRadius: 0).fill()
|
||||
// print("drawing")
|
||||
let fs = 36 as CGFloat
|
||||
let hFont = UIFont.systemFont(ofSize: fs)
|
||||
var attrib = [
|
||||
NSAttributedString.Key.font: hFont,
|
||||
NSAttributedString.Key.foregroundColor: UIColor.gray
|
||||
]
|
||||
|
||||
let str = "" as NSString
|
||||
let actualHeight = str.size(withAttributes: attrib).height
|
||||
attrib[NSAttributedString.Key.font] = hFont.withSize(fs * fs / actualHeight)
|
||||
|
||||
let strW = str.size(withAttributes: attrib).width
|
||||
str.draw(at: CGPoint(x: (rect.size.width - strW) / 2.0, y: -3), withAttributes: attrib)
|
||||
|
||||
let img = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return img
|
||||
}
|
||||
|
||||
func generateUnknownIcon() -> UIImage? {
|
||||
let rect = CGRect(x: 0, y: 0, width: 30, height: 30)
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
let lineWidth: CGFloat = 0.5
|
||||
let corner: CGFloat = 6.75
|
||||
let c = corner / CGFloat.pi + lineWidth/2
|
||||
let sz: CGFloat = rect.height
|
||||
let m = sz / 2
|
||||
let r1 = 0.2 * sz, r2 = sqrt(2 * r1 * r1)
|
||||
|
||||
// diagonal
|
||||
context.lineFromTo(x1: c, y1: c, x2: sz-c, y2: sz-c)
|
||||
context.lineFromTo(x1: c, y1: sz-c, x2: sz-c, y2: c)
|
||||
// horizontal
|
||||
context.lineFromTo(x1: 0, y1: m, x2: sz, y2: m)
|
||||
context.lineFromTo(x1: 0, y1: m + r1, x2: sz, y2: m + r1)
|
||||
context.lineFromTo(x1: 0, y1: m - r1, x2: sz, y2: m - r1)
|
||||
// vertical
|
||||
context.lineFromTo(x1: m, y1: 0, x2: m, y2: sz)
|
||||
context.lineFromTo(x1: m + r1, y1: 0, x2: m + r1, y2: sz)
|
||||
context.lineFromTo(x1: m - r1, y1: 0, x2: m - r1, y2: sz)
|
||||
// circles
|
||||
context.addEllipse(in: CGRect(x: m - r1, y: m - r1, width: 2*r1, height: 2*r1))
|
||||
context.addEllipse(in: CGRect(x: m - r2, y: m - r2, width: 2*r2, height: 2*r2))
|
||||
let r3 = CGRect(x: c, y: c, width: sz - 2*c, height: sz - 2*c)
|
||||
context.addEllipse(in: r3)
|
||||
context.addRect(r3)
|
||||
|
||||
UIColor.clear.setFill()
|
||||
UIColor.gray.setStroke()
|
||||
let rounded = UIBezierPath(roundedRect: rect.insetBy(dx: lineWidth/2, dy: lineWidth/2), cornerRadius: corner)
|
||||
rounded.lineWidth = lineWidth
|
||||
rounded.stroke()
|
||||
|
||||
let img = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return img
|
||||
}
|
||||
|
||||
extension CGContext {
|
||||
func lineFromTo(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) {
|
||||
self.move(to: CGPoint(x: x1, y: y1))
|
||||
self.addLine(to: CGPoint(x: x2, y: y2))
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,31 @@ br.vet
|
||||
br.vlog
|
||||
br.wiki
|
||||
br.zlg
|
||||
co.a
|
||||
co.b
|
||||
co.com
|
||||
co.edu
|
||||
co.g
|
||||
co.gov
|
||||
co.inf
|
||||
co.m
|
||||
co.mil
|
||||
co.net
|
||||
co.ngo
|
||||
co.nom
|
||||
co.o
|
||||
co.org
|
||||
co.s
|
||||
co.t
|
||||
co.x
|
||||
co.y
|
||||
er.com
|
||||
er.edu
|
||||
er.gov
|
||||
er.mil
|
||||
er.net
|
||||
er.org
|
||||
er.ind
|
||||
es.com
|
||||
es.edu
|
||||
es.gob
|
||||
@@ -553,4 +578,4 @@ za.nom
|
||||
za.org
|
||||
za.school
|
||||
za.tm
|
||||
za.web
|
||||
za.web
|
||||
|
||||
11
media/tutorials/tut-cooccurrence.md
Normal file
@@ -0,0 +1,11 @@
|
||||
___Co-Occurrence___ allows you to find requests that happen often at the same time as the selected domain. Hence it will give you a hint what Apps might be involved in the activity.
|
||||
|
||||
How do you interpret these results? Lets look at an example:
|
||||
|
||||
<IMG>
|
||||
|
||||
The domain __example.org__ had __14__ requests with an _average time divergence_ of __0.71 seconds__. That is, these 14 domain calls happend, on average, less then a second before or after the original request of the selected domain.
|
||||
|
||||
Close temporal proximity and high occurrence counts are both indicators for domain correlation. Results are sorted by a ranking index (__9.__) which strikes a balance between the two. Preferring entries with higher counts as well as low time divergence.
|
||||
|
||||
_Tip:_ As a visual guide you can look for the colored bar beside each value. The larger the bar, the greater the correlation.
|
||||
5
media/tutorials/tut-recording-1.md
Normal file
@@ -0,0 +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 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.
|
||||
5
media/tutorials/tut-recording-2.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Contribute
|
||||
|
||||
This step is completely __optional__. You can choose to share your results with us. We can compare similar applications and suggest privacy friendly alternatives. Together with other likeminded individuals we can increase the awareness for privacy friendly design.
|
||||
|
||||
Thank you very much.
|
||||
20
media/tutorials/tut-recording-howto.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# How to record?
|
||||
|
||||
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
|
||||
|
||||
During the recording all logging filter are paused. This means blocked domains are not being blocked and ignored domains show up regardless. This is necessary for comparability reasons.
|
||||
|
||||
## App recording
|
||||
|
||||
Before you begin make sure that you quit all running applications and wait a few seconds. Tap on the 'App' recording button and switch to the application you'd like to inspect. Use the App as you would normally. Try to get to all corners and functionality the App provides. When you feel that you have captured enough content, come back to _AppCheck_ and stop the recording.
|
||||
|
||||
## Background recording
|
||||
|
||||
Will answer one simple question: What communications happen while you aren't using your device. You should solely start a background recording when you know you aren't going to use your device in the near future. For example, before you go to bed.
|
||||
As soon as you start using your device, you should stop the recording to avoid distorting the results.
|
||||
|
||||
## Afterwards
|
||||
|
||||
Upon completion you will find your recording in the section below. You can review your results and remove any user specific information if necessary.
|
||||
5
media/tutorials/tut-welcome-1.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Welcome
|
||||
|
||||
AppCheck helps you identify which applications communicate with third parties. It does so by logging network requests. AppCheck learns only the destination addresses, not the actual data that is exchanged.
|
||||
|
||||
Your data belongs to you. Therefore, monitoring and analysis take place on your device only. The app does not share any data with us or any other third-party. Unless you choose to.
|
||||
4
media/tutorials/tut-welcome-2.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# How it works
|
||||
|
||||
AppCheck creates a local VPN tunnel to intercept all network connections. For each connection AppCheck looks into the DNS headers only, namely the domain names.
|
||||
These domain names are logged in the background while the VPN is active. That means, AppCheck does not have to be active in the foreground. You can close the app and come back later to see the results.
|
||||