Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e54d69ef4b | ||
|
|
be8269ad56 | ||
|
|
7118ec3b02 | ||
|
|
71045bf0dd | ||
|
|
27abdd66f5 | ||
|
|
162e18c912 | ||
|
|
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 | ||
|
|
b270f30f3c | ||
|
|
03177cee0b | ||
|
|
9ee094dc20 | ||
|
|
b1d49c6765 | ||
|
|
b774e2152c | ||
|
|
e398ac8bcd | ||
|
|
01523b250f | ||
|
|
a2b0f311d5 |
@@ -13,7 +13,18 @@
|
||||
540E6780242D2CF100871BBE /* VCRecordings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E677F242D2CF100871BBE /* VCRecordings.swift */; };
|
||||
540E67822433483D00871BBE /* VCEditRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E67812433483D00871BBE /* VCEditRecording.swift */; };
|
||||
540E67842433FAFE00871BBE /* TVCPreviousRecords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */; };
|
||||
541075CE24C9D43A00D6F1BF /* UNNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075CD24C9D43A00D6F1BF /* UNNotification.swift */; };
|
||||
541075CF24C9D43A00D6F1BF /* UNNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075CD24C9D43A00D6F1BF /* UNNotification.swift */; };
|
||||
541075D124CDBA0000D6F1BF /* ThrottledBatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D024CDBA0000D6F1BF /* ThrottledBatchQueue.swift */; };
|
||||
541075D224CDBA0000D6F1BF /* ThrottledBatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D024CDBA0000D6F1BF /* ThrottledBatchQueue.swift */; };
|
||||
541075D524CE286200D6F1BF /* CachedConnectionAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D424CE286200D6F1BF /* CachedConnectionAlert.swift */; };
|
||||
541075D624CE286200D6F1BF /* CachedConnectionAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D424CE286200D6F1BF /* CachedConnectionAlert.swift */; };
|
||||
541075D924CE2C7200D6F1BF /* GlassVPNHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D824CE2C7200D6F1BF /* GlassVPNHook.swift */; };
|
||||
541075DA24CE2C7200D6F1BF /* GlassVPNHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541075D824CE2C7200D6F1BF /* GlassVPNHook.swift */; };
|
||||
5412F8EE24571B8200A63D7A /* VCDateFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412F8ED24571B8100A63D7A /* VCDateFilter.swift */; };
|
||||
5412FCC224C628FA000DE429 /* TVCReminderAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412FCBF24C628F9000DE429 /* TVCReminderAlerts.swift */; };
|
||||
5412FCC324C628FA000DE429 /* TVCChooseAlertTone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412FCC024C628F9000DE429 /* TVCChooseAlertTone.swift */; };
|
||||
5412FCC424C628FA000DE429 /* TVCConnectionAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412FCC124C628FA000DE429 /* TVCConnectionAlerts.swift */; };
|
||||
541A957623E602DF00C09C19 /* LaunchIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 541A957523E602DF00C09C19 /* LaunchIcon.png */; };
|
||||
541AC5D82399498A00A769D7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541AC5D72399498A00A769D7 /* AppDelegate.swift */; };
|
||||
541AC5DD2399498A00A769D7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 541AC5DB2399498A00A769D7 /* Main.storyboard */; };
|
||||
@@ -24,6 +35,30 @@
|
||||
541FC47824A1453F009154D8 /* VCCoOccurrence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541FC47724A1453F009154D8 /* VCCoOccurrence.swift */; };
|
||||
541FC9872497D81C00962623 /* TheGreatDestroyer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541FC9862497D81C00962623 /* TheGreatDestroyer.swift */; };
|
||||
542E2A982404973F001462DC /* TBCMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542E2A972404973F001462DC /* TBCMain.swift */; };
|
||||
543078AA24B5E12500278F2D /* snap2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 5430789F24B5E12200278F2D /* snap2.caf */; };
|
||||
543078AB24B5E12500278F2D /* snap2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 5430789F24B5E12200278F2D /* snap2.caf */; };
|
||||
543078AC24B5E12500278F2D /* typewriter2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A024B5E12200278F2D /* typewriter2.caf */; };
|
||||
543078AD24B5E12500278F2D /* typewriter2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A024B5E12200278F2D /* typewriter2.caf */; };
|
||||
543078AE24B5E12500278F2D /* wood1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A124B5E12300278F2D /* wood1.caf */; };
|
||||
543078AF24B5E12500278F2D /* wood1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A124B5E12300278F2D /* wood1.caf */; };
|
||||
543078B024B5E12500278F2D /* plop2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A224B5E12300278F2D /* plop2.caf */; };
|
||||
543078B124B5E12500278F2D /* plop2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A224B5E12300278F2D /* plop2.caf */; };
|
||||
543078B224B5E12500278F2D /* plop1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A324B5E12300278F2D /* plop1.caf */; };
|
||||
543078B324B5E12500278F2D /* plop1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A324B5E12300278F2D /* plop1.caf */; };
|
||||
543078B424B5E12500278F2D /* snap1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A424B5E12300278F2D /* snap1.caf */; };
|
||||
543078B524B5E12500278F2D /* snap1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A424B5E12300278F2D /* snap1.caf */; };
|
||||
543078B624B5E12500278F2D /* drum1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A524B5E12300278F2D /* drum1.caf */; };
|
||||
543078B724B5E12500278F2D /* drum1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A524B5E12300278F2D /* drum1.caf */; };
|
||||
543078B824B5E12500278F2D /* wood2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A624B5E12400278F2D /* wood2.caf */; };
|
||||
543078B924B5E12500278F2D /* wood2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A624B5E12400278F2D /* wood2.caf */; };
|
||||
543078BA24B5E12500278F2D /* typewriter1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A724B5E12400278F2D /* typewriter1.caf */; };
|
||||
543078BB24B5E12500278F2D /* typewriter1.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A724B5E12400278F2D /* typewriter1.caf */; };
|
||||
543078BC24B5E12500278F2D /* clock.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A824B5E12400278F2D /* clock.caf */; };
|
||||
543078BD24B5E12500278F2D /* clock.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A824B5E12400278F2D /* clock.caf */; };
|
||||
543078BE24B5E12500278F2D /* drum2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A924B5E12500278F2D /* drum2.caf */; };
|
||||
543078BF24B5E12500278F2D /* drum2.caf in Resources */ = {isa = PBXBuildFile; fileRef = 543078A924B5E12500278F2D /* drum2.caf */; };
|
||||
543078C324B60F3B00278F2D /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 543078C124B60F3B00278F2D /* Settings.storyboard */; };
|
||||
543078C924B75CEA00278F2D /* PushNotificationAppOnly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543078C824B75CD100278F2D /* PushNotificationAppOnly.swift */; };
|
||||
543CDB2023EEE61900B7F323 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543CDB1F23EEE61900B7F323 /* PacketTunnelProvider.swift */; };
|
||||
543CDB2523EEE61900B7F323 /* GlassVPN.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 543CDB1D23EEE61900B7F323 /* GlassVPN.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
54448A2E2486464F00771C96 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54448A2D2486464F00771C96 /* Array.swift */; };
|
||||
@@ -35,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 */; };
|
||||
@@ -42,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 */; };
|
||||
@@ -50,16 +98,14 @@
|
||||
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 */; };
|
||||
54CA025C2426B2FD003A5E04 /* ConnectSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E22426B2FC003A5E04 /* ConnectSession.swift */; };
|
||||
54CA025D2426B2FD003A5E04 /* HTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E32426B2FC003A5E04 /* HTTPHeader.swift */; };
|
||||
54CA025E2426B2FD003A5E04 /* ResponseGeneratorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E42426B2FC003A5E04 /* ResponseGeneratorFactory.swift */; };
|
||||
54CA025F2426B2FD003A5E04 /* ProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E62426B2FC003A5E04 /* ProxyServer.swift */; };
|
||||
54CA02602426B2FD003A5E04 /* GCDProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E72426B2FC003A5E04 /* GCDProxyServer.swift */; };
|
||||
54CA02612426B2FD003A5E04 /* GCDSOCKS5ProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E82426B2FC003A5E04 /* GCDSOCKS5ProxyServer.swift */; };
|
||||
54CA02622426B2FD003A5E04 /* GCDHTTPProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01E92426B2FC003A5E04 /* GCDHTTPProxyServer.swift */; };
|
||||
54CA02662426B2FD003A5E04 /* NWUDPSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01EF2426B2FC003A5E04 /* NWUDPSocket.swift */; };
|
||||
54CA02672426B2FD003A5E04 /* RawTCPSocketProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA01F02426B2FC003A5E04 /* RawTCPSocketProtocol.swift */; };
|
||||
@@ -81,15 +127,8 @@
|
||||
54CA027B2426B2FD003A5E04 /* HTTPAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02062426B2FC003A5E04 /* HTTPAuthentication.swift */; };
|
||||
54CA027C2426B2FD003A5E04 /* StreamScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02072426B2FC003A5E04 /* StreamScanner.swift */; };
|
||||
54CA027D2426B2FD003A5E04 /* GlobalIntializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02082426B2FC003A5E04 /* GlobalIntializer.swift */; };
|
||||
54CA027E2426B2FD003A5E04 /* DomainListRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020A2426B2FC003A5E04 /* DomainListRule.swift */; };
|
||||
54CA02802426B2FD003A5E04 /* DNSSessionMatchType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020C2426B2FC003A5E04 /* DNSSessionMatchType.swift */; };
|
||||
54CA02812426B2FD003A5E04 /* DNSFailRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020D2426B2FC003A5E04 /* DNSFailRule.swift */; };
|
||||
54CA02822426B2FD003A5E04 /* AllRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020E2426B2FC003A5E04 /* AllRule.swift */; };
|
||||
54CA02832426B2FD003A5E04 /* DNSSessionMatchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA020F2426B2FC003A5E04 /* DNSSessionMatchResult.swift */; };
|
||||
54CA02842426B2FD003A5E04 /* Rule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02102426B2FC003A5E04 /* Rule.swift */; };
|
||||
54CA02852426B2FD003A5E04 /* DirectRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02112426B2FC003A5E04 /* DirectRule.swift */; };
|
||||
54CA02862426B2FD003A5E04 /* RuleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02122426B2FC003A5E04 /* RuleManager.swift */; };
|
||||
54CA02872426B2FD003A5E04 /* IPRangeListRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02132426B2FC003A5E04 /* IPRangeListRule.swift */; };
|
||||
54CA02882426B2FD003A5E04 /* QueueFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02152426B2FC003A5E04 /* QueueFactory.swift */; };
|
||||
54CA02892426B2FD003A5E04 /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02162426B2FC003A5E04 /* Tunnel.swift */; };
|
||||
54CA028A2426B2FD003A5E04 /* ResponseGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02172426B2FC003A5E04 /* ResponseGenerator.swift */; };
|
||||
@@ -108,30 +147,19 @@
|
||||
54CA029E2426B2FD003A5E04 /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02302426B2FC003A5E04 /* EventType.swift */; };
|
||||
54CA029F2426B2FD003A5E04 /* ProxySocketEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02312426B2FC003A5E04 /* ProxySocketEvent.swift */; };
|
||||
54CA02A02426B2FD003A5E04 /* TunnelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02322426B2FC003A5E04 /* TunnelEvent.swift */; };
|
||||
54CA02A12426B2FD003A5E04 /* RuleMatchEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02332426B2FC003A5E04 /* RuleMatchEvent.swift */; };
|
||||
54CA02A22426B2FD003A5E04 /* ObserverFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02342426B2FC003A5E04 /* ObserverFactory.swift */; };
|
||||
54CA02A32426B2FD003A5E04 /* HTTPAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02372426B2FC003A5E04 /* HTTPAdapter.swift */; };
|
||||
54CA02A42426B2FD003A5E04 /* SecureHTTPAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02382426B2FC003A5E04 /* SecureHTTPAdapter.swift */; };
|
||||
54CA02A62426B2FD003A5E04 /* AdapterSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023A2426B2FC003A5E04 /* AdapterSocket.swift */; };
|
||||
54CA02A72426B2FD003A5E04 /* DirectAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023B2426B2FC003A5E04 /* DirectAdapter.swift */; };
|
||||
54CA02A82426B2FD003A5E04 /* SOCKS5Adapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023C2426B2FC003A5E04 /* SOCKS5Adapter.swift */; };
|
||||
54CA02A92426B2FD003A5E04 /* RejectAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023D2426B2FC003A5E04 /* RejectAdapter.swift */; };
|
||||
54CA02AC2426B2FD003A5E04 /* AuthenticationServerAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02412426B2FC003A5E04 /* AuthenticationServerAdapterFactory.swift */; };
|
||||
54CA02AD2426B2FD003A5E04 /* RejectAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02422426B2FC003A5E04 /* RejectAdapterFactory.swift */; };
|
||||
54CA02AE2426B2FD003A5E04 /* AdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02432426B2FD003A5E04 /* AdapterFactory.swift */; };
|
||||
54CA02AF2426B2FD003A5E04 /* SOCKS5AdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02442426B2FD003A5E04 /* SOCKS5AdapterFactory.swift */; };
|
||||
54CA02B02426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02452426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift */; };
|
||||
54CA02B12426B2FD003A5E04 /* ServerAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02462426B2FD003A5E04 /* ServerAdapterFactory.swift */; };
|
||||
54CA02B22426B2FD003A5E04 /* AdapterFactoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02472426B2FD003A5E04 /* AdapterFactoryManager.swift */; };
|
||||
54CA02B32426B2FD003A5E04 /* HTTPAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02482426B2FD003A5E04 /* HTTPAdapterFactory.swift */; };
|
||||
54CA02B82426B2FD003A5E04 /* HTTPProxySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA024F2426B2FD003A5E04 /* HTTPProxySocket.swift */; };
|
||||
54CA02B92426B2FD003A5E04 /* DirectProxySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02502426B2FD003A5E04 /* DirectProxySocket.swift */; };
|
||||
54CA02BA2426B2FD003A5E04 /* ProxySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02512426B2FD003A5E04 /* ProxySocket.swift */; };
|
||||
54CA02BB2426B2FD003A5E04 /* SOCKS5ProxySocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02522426B2FD003A5E04 /* SOCKS5ProxySocket.swift */; };
|
||||
54CA02BC2426B2FD003A5E04 /* SocketProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02532426B2FD003A5E04 /* SocketProtocol.swift */; };
|
||||
54CA02BE2426D4F3003A5E04 /* DDLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02BD2426D4F3003A5E04 /* DDLog.swift */; };
|
||||
54CA02C32426DCCD003A5E04 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02BF2426DCCC003A5E04 /* GCDAsyncSocket.m */; };
|
||||
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 */; };
|
||||
@@ -139,7 +167,7 @@
|
||||
54D8B9832471BD8100EB2414 /* DBAppOnly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B9822471BD8100EB2414 /* DBAppOnly.swift */; };
|
||||
54D8B98624796E9900EB2414 /* GroupedDomainDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D8B98524796E9800EB2414 /* GroupedDomainDataSource.swift */; };
|
||||
54E540F2247C423200F7C34A /* DomainFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F1247C423200F7C34A /* DomainFilter.swift */; };
|
||||
54E540F4247D3F2600F7C34A /* TestDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F3247D3F2600F7C34A /* TestDataSource.swift */; };
|
||||
54E540F4247D3F2600F7C34A /* SimulatorVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F3247D3F2600F7C34A /* SimulatorVPN.swift */; };
|
||||
54E540F8247DB90F00F7C34A /* RecordingsDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F7247DB90F00F7C34A /* RecordingsDB.swift */; };
|
||||
54E540FA2482414800F7C34A /* SyncUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F92482414800F7C34A /* SyncUpdate.swift */; };
|
||||
54E67E4624A8B0FE0025D261 /* PrefsShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4524A8B0FE0025D261 /* PrefsShared.swift */; };
|
||||
@@ -181,7 +209,14 @@
|
||||
540E677F242D2CF100871BBE /* VCRecordings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCRecordings.swift; sourceTree = "<group>"; };
|
||||
540E67812433483D00871BBE /* VCEditRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCEditRecording.swift; sourceTree = "<group>"; };
|
||||
540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCPreviousRecords.swift; sourceTree = "<group>"; };
|
||||
541075CD24C9D43A00D6F1BF /* UNNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotification.swift; sourceTree = "<group>"; };
|
||||
541075D024CDBA0000D6F1BF /* ThrottledBatchQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThrottledBatchQueue.swift; sourceTree = "<group>"; };
|
||||
541075D424CE286200D6F1BF /* CachedConnectionAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedConnectionAlert.swift; sourceTree = "<group>"; };
|
||||
541075D824CE2C7200D6F1BF /* GlassVPNHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlassVPNHook.swift; sourceTree = "<group>"; };
|
||||
5412F8ED24571B8100A63D7A /* VCDateFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCDateFilter.swift; sourceTree = "<group>"; };
|
||||
5412FCBF24C628F9000DE429 /* TVCReminderAlerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TVCReminderAlerts.swift; sourceTree = "<group>"; };
|
||||
5412FCC024C628F9000DE429 /* TVCChooseAlertTone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TVCChooseAlertTone.swift; sourceTree = "<group>"; };
|
||||
5412FCC124C628FA000DE429 /* TVCConnectionAlerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TVCConnectionAlerts.swift; sourceTree = "<group>"; };
|
||||
541A957523E602DF00C09C19 /* LaunchIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = LaunchIcon.png; sourceTree = "<group>"; };
|
||||
541AC5D42399498A00A769D7 /* AppCheck.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppCheck.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
541AC5D72399498A00A769D7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -195,6 +230,19 @@
|
||||
541FC9862497D81C00962623 /* TheGreatDestroyer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TheGreatDestroyer.swift; sourceTree = "<group>"; };
|
||||
542E2A972404973F001462DC /* TBCMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TBCMain.swift; sourceTree = "<group>"; };
|
||||
542E2A9924051556001462DC /* TVCSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCSettings.swift; sourceTree = "<group>"; };
|
||||
5430789F24B5E12200278F2D /* snap2.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = snap2.caf; sourceTree = "<group>"; };
|
||||
543078A024B5E12200278F2D /* typewriter2.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = typewriter2.caf; sourceTree = "<group>"; };
|
||||
543078A124B5E12300278F2D /* wood1.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = wood1.caf; sourceTree = "<group>"; };
|
||||
543078A224B5E12300278F2D /* plop2.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = plop2.caf; sourceTree = "<group>"; };
|
||||
543078A324B5E12300278F2D /* plop1.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = plop1.caf; sourceTree = "<group>"; };
|
||||
543078A424B5E12300278F2D /* snap1.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = snap1.caf; sourceTree = "<group>"; };
|
||||
543078A524B5E12300278F2D /* drum1.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = drum1.caf; sourceTree = "<group>"; };
|
||||
543078A624B5E12400278F2D /* wood2.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = wood2.caf; sourceTree = "<group>"; };
|
||||
543078A724B5E12400278F2D /* typewriter1.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = typewriter1.caf; sourceTree = "<group>"; };
|
||||
543078A824B5E12400278F2D /* clock.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = clock.caf; sourceTree = "<group>"; };
|
||||
543078A924B5E12500278F2D /* drum2.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = drum2.caf; sourceTree = "<group>"; };
|
||||
543078C224B60F3B00278F2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Settings.storyboard; sourceTree = "<group>"; };
|
||||
543078C824B75CD100278F2D /* PushNotificationAppOnly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationAppOnly.swift; sourceTree = "<group>"; };
|
||||
543CDB1D23EEE61900B7F323 /* GlassVPN.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = GlassVPN.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
543CDB1F23EEE61900B7F323 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
||||
543CDB2123EEE61900B7F323 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -207,13 +255,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>"; };
|
||||
@@ -222,17 +283,15 @@
|
||||
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>"; };
|
||||
54CA01D42426B251003A5E04 /* SafeDict.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SafeDict.swift; sourceTree = "<group>"; };
|
||||
54CA01E22426B2FC003A5E04 /* ConnectSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectSession.swift; sourceTree = "<group>"; };
|
||||
54CA01E32426B2FC003A5E04 /* HTTPHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPHeader.swift; sourceTree = "<group>"; };
|
||||
54CA01E42426B2FC003A5E04 /* ResponseGeneratorFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseGeneratorFactory.swift; sourceTree = "<group>"; };
|
||||
54CA01E62426B2FC003A5E04 /* ProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxyServer.swift; sourceTree = "<group>"; };
|
||||
54CA01E72426B2FC003A5E04 /* GCDProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GCDProxyServer.swift; sourceTree = "<group>"; };
|
||||
54CA01E82426B2FC003A5E04 /* GCDSOCKS5ProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GCDSOCKS5ProxyServer.swift; sourceTree = "<group>"; };
|
||||
54CA01E92426B2FC003A5E04 /* GCDHTTPProxyServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GCDHTTPProxyServer.swift; sourceTree = "<group>"; };
|
||||
54CA01EF2426B2FC003A5E04 /* NWUDPSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NWUDPSocket.swift; sourceTree = "<group>"; };
|
||||
54CA01F02426B2FC003A5E04 /* RawTCPSocketProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawTCPSocketProtocol.swift; sourceTree = "<group>"; };
|
||||
@@ -254,15 +313,8 @@
|
||||
54CA02062426B2FC003A5E04 /* HTTPAuthentication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPAuthentication.swift; sourceTree = "<group>"; };
|
||||
54CA02072426B2FC003A5E04 /* StreamScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamScanner.swift; sourceTree = "<group>"; };
|
||||
54CA02082426B2FC003A5E04 /* GlobalIntializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalIntializer.swift; sourceTree = "<group>"; };
|
||||
54CA020A2426B2FC003A5E04 /* DomainListRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListRule.swift; sourceTree = "<group>"; };
|
||||
54CA020C2426B2FC003A5E04 /* DNSSessionMatchType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSSessionMatchType.swift; sourceTree = "<group>"; };
|
||||
54CA020D2426B2FC003A5E04 /* DNSFailRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSFailRule.swift; sourceTree = "<group>"; };
|
||||
54CA020E2426B2FC003A5E04 /* AllRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllRule.swift; sourceTree = "<group>"; };
|
||||
54CA020F2426B2FC003A5E04 /* DNSSessionMatchResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSSessionMatchResult.swift; sourceTree = "<group>"; };
|
||||
54CA02102426B2FC003A5E04 /* Rule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Rule.swift; sourceTree = "<group>"; };
|
||||
54CA02112426B2FC003A5E04 /* DirectRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectRule.swift; sourceTree = "<group>"; };
|
||||
54CA02122426B2FC003A5E04 /* RuleManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleManager.swift; sourceTree = "<group>"; };
|
||||
54CA02132426B2FC003A5E04 /* IPRangeListRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPRangeListRule.swift; sourceTree = "<group>"; };
|
||||
54CA02152426B2FC003A5E04 /* QueueFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueueFactory.swift; sourceTree = "<group>"; };
|
||||
54CA02162426B2FC003A5E04 /* Tunnel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = "<group>"; };
|
||||
54CA02172426B2FC003A5E04 /* ResponseGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseGenerator.swift; sourceTree = "<group>"; };
|
||||
@@ -281,39 +333,27 @@
|
||||
54CA02302426B2FC003A5E04 /* EventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventType.swift; sourceTree = "<group>"; };
|
||||
54CA02312426B2FC003A5E04 /* ProxySocketEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxySocketEvent.swift; sourceTree = "<group>"; };
|
||||
54CA02322426B2FC003A5E04 /* TunnelEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelEvent.swift; sourceTree = "<group>"; };
|
||||
54CA02332426B2FC003A5E04 /* RuleMatchEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleMatchEvent.swift; sourceTree = "<group>"; };
|
||||
54CA02342426B2FC003A5E04 /* ObserverFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverFactory.swift; sourceTree = "<group>"; };
|
||||
54CA02372426B2FC003A5E04 /* HTTPAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPAdapter.swift; sourceTree = "<group>"; };
|
||||
54CA02382426B2FC003A5E04 /* SecureHTTPAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureHTTPAdapter.swift; sourceTree = "<group>"; };
|
||||
54CA023A2426B2FC003A5E04 /* AdapterSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterSocket.swift; sourceTree = "<group>"; };
|
||||
54CA023B2426B2FC003A5E04 /* DirectAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectAdapter.swift; sourceTree = "<group>"; };
|
||||
54CA023C2426B2FC003A5E04 /* SOCKS5Adapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SOCKS5Adapter.swift; sourceTree = "<group>"; };
|
||||
54CA023D2426B2FC003A5E04 /* RejectAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RejectAdapter.swift; sourceTree = "<group>"; };
|
||||
54CA02412426B2FC003A5E04 /* AuthenticationServerAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationServerAdapterFactory.swift; sourceTree = "<group>"; };
|
||||
54CA02422426B2FC003A5E04 /* RejectAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RejectAdapterFactory.swift; sourceTree = "<group>"; };
|
||||
54CA02432426B2FD003A5E04 /* AdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterFactory.swift; sourceTree = "<group>"; };
|
||||
54CA02442426B2FD003A5E04 /* SOCKS5AdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SOCKS5AdapterFactory.swift; sourceTree = "<group>"; };
|
||||
54CA02452426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureHTTPAdapterFactory.swift; sourceTree = "<group>"; };
|
||||
54CA02462426B2FD003A5E04 /* ServerAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerAdapterFactory.swift; sourceTree = "<group>"; };
|
||||
54CA02472426B2FD003A5E04 /* AdapterFactoryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterFactoryManager.swift; sourceTree = "<group>"; };
|
||||
54CA02482426B2FD003A5E04 /* HTTPAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPAdapterFactory.swift; sourceTree = "<group>"; };
|
||||
54CA024F2426B2FD003A5E04 /* HTTPProxySocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPProxySocket.swift; sourceTree = "<group>"; };
|
||||
54CA02502426B2FD003A5E04 /* DirectProxySocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectProxySocket.swift; sourceTree = "<group>"; };
|
||||
54CA02512426B2FD003A5E04 /* ProxySocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxySocket.swift; sourceTree = "<group>"; };
|
||||
54CA02522426B2FD003A5E04 /* SOCKS5ProxySocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SOCKS5ProxySocket.swift; sourceTree = "<group>"; };
|
||||
54CA02532426B2FD003A5E04 /* SocketProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketProtocol.swift; sourceTree = "<group>"; };
|
||||
54CA02BD2426D4F3003A5E04 /* DDLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDLog.swift; sourceTree = "<group>"; };
|
||||
54CA02BF2426DCCC003A5E04 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = "<group>"; };
|
||||
54CA02C02426DCCD003A5E04 /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncUdpSocket.m; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
54D8B9822471BD8100EB2414 /* DBAppOnly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBAppOnly.swift; sourceTree = "<group>"; };
|
||||
54D8B98524796E9800EB2414 /* GroupedDomainDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupedDomainDataSource.swift; sourceTree = "<group>"; };
|
||||
54E540F1247C423200F7C34A /* DomainFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainFilter.swift; sourceTree = "<group>"; };
|
||||
54E540F3247D3F2600F7C34A /* TestDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDataSource.swift; sourceTree = "<group>"; };
|
||||
54E540F3247D3F2600F7C34A /* SimulatorVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorVPN.swift; sourceTree = "<group>"; };
|
||||
54E540F7247DB90F00F7C34A /* RecordingsDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingsDB.swift; sourceTree = "<group>"; };
|
||||
54E540F92482414800F7C34A /* SyncUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpdate.swift; sourceTree = "<group>"; };
|
||||
54E67E4524A8B0FE0025D261 /* PrefsShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsShared.swift; sourceTree = "<group>"; };
|
||||
@@ -360,6 +400,9 @@
|
||||
children = (
|
||||
542E2A9924051556001462DC /* TVCSettings.swift */,
|
||||
54B34593240E6343004C53CC /* TVCFilter.swift */,
|
||||
5412FCBF24C628F9000DE429 /* TVCReminderAlerts.swift */,
|
||||
5412FCC124C628FA000DE429 /* TVCConnectionAlerts.swift */,
|
||||
5412FCC024C628F9000DE429 /* TVCChooseAlertTone.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
@@ -369,12 +412,27 @@
|
||||
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>";
|
||||
};
|
||||
541075D324CE284700D6F1BF /* Push Notifications */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
541075CD24C9D43A00D6F1BF /* UNNotification.swift */,
|
||||
54CE8BC324B1ED2100CC1756 /* PushNotification.swift */,
|
||||
543078C824B75CD100278F2D /* PushNotificationAppOnly.swift */,
|
||||
541075D424CE286200D6F1BF /* CachedConnectionAlert.swift */,
|
||||
);
|
||||
path = "Push Notifications";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
541AC5CB2399498A00A769D7 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -401,16 +459,16 @@
|
||||
54E540F0247C386500F7C34A /* Data Source */,
|
||||
54B345A4241BB975004C53CC /* Extensions */,
|
||||
545DDDD224436A03003B6544 /* Common Classes */,
|
||||
541075D324CE284700D6F1BF /* Push Notifications */,
|
||||
548B1F9423D338EC005B047C /* main.entitlements */,
|
||||
541AC5D72399498A00A769D7 /* AppDelegate.swift */,
|
||||
54E67E4A24A8C6370025D261 /* GlassVPN.swift */,
|
||||
541075D824CE2C7200D6F1BF /* GlassVPNHook.swift */,
|
||||
542E2A972404973F001462DC /* TBCMain.swift */,
|
||||
540C6454240D5BAE00E948F9 /* Requests */,
|
||||
540E677E242D2CD200871BBE /* Recordings */,
|
||||
540C6455240D5BD200E948F9 /* Settings */,
|
||||
54B345B12422E029004C53CC /* unused */,
|
||||
541AC5E02399498B00A769D7 /* LaunchScreen.storyboard */,
|
||||
541AC5DB2399498A00A769D7 /* Main.storyboard */,
|
||||
54A0CC0D24E314B6009B5EC1 /* GUI */,
|
||||
541AC5DE2399498B00A769D7 /* Assets.xcassets */,
|
||||
541AC5E32399498B00A769D7 /* Info.plist */,
|
||||
54953E7023E473F10054345C /* Settings.bundle */,
|
||||
@@ -430,12 +488,32 @@
|
||||
542E2A9B24051F79001462DC /* media */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54686A8324FD0A3F0084934D /* tutorials */,
|
||||
5430789E24B5E10E00278F2D /* sounds */,
|
||||
541A957523E602DF00C09C19 /* LaunchIcon.png */,
|
||||
54B345AF242264F8004C53CC /* third-level.txt */,
|
||||
);
|
||||
path = media;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5430789E24B5E10E00278F2D /* sounds */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
543078A824B5E12400278F2D /* clock.caf */,
|
||||
543078A524B5E12300278F2D /* drum1.caf */,
|
||||
543078A924B5E12500278F2D /* drum2.caf */,
|
||||
543078A324B5E12300278F2D /* plop1.caf */,
|
||||
543078A224B5E12300278F2D /* plop2.caf */,
|
||||
543078A424B5E12300278F2D /* snap1.caf */,
|
||||
5430789F24B5E12200278F2D /* snap2.caf */,
|
||||
543078A724B5E12400278F2D /* typewriter1.caf */,
|
||||
543078A024B5E12200278F2D /* typewriter2.caf */,
|
||||
543078A124B5E12300278F2D /* wood1.caf */,
|
||||
543078A624B5E12400278F2D /* wood2.caf */,
|
||||
);
|
||||
path = sounds;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
543CDB1E23EEE61900B7F323 /* GlassVPN */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -457,15 +535,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 = (
|
||||
@@ -498,13 +605,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 */ = {
|
||||
@@ -532,7 +639,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54CA01E12426B2FC003A5E04 /* Messages */,
|
||||
54CA01E42426B2FC003A5E04 /* ResponseGeneratorFactory.swift */,
|
||||
54CA01E52426B2FC003A5E04 /* ProxyServer */,
|
||||
54CA01EE2426B2FC003A5E04 /* RawSocket */,
|
||||
54CA01F72426B2FC003A5E04 /* Opt.swift */,
|
||||
@@ -563,7 +669,6 @@
|
||||
children = (
|
||||
54CA01E62426B2FC003A5E04 /* ProxyServer.swift */,
|
||||
54CA01E72426B2FC003A5E04 /* GCDProxyServer.swift */,
|
||||
54CA01E82426B2FC003A5E04 /* GCDSOCKS5ProxyServer.swift */,
|
||||
54CA01E92426B2FC003A5E04 /* GCDHTTPProxyServer.swift */,
|
||||
);
|
||||
path = ProxyServer;
|
||||
@@ -604,15 +709,8 @@
|
||||
54CA02092426B2FC003A5E04 /* Rule */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54CA020A2426B2FC003A5E04 /* DomainListRule.swift */,
|
||||
54CA020C2426B2FC003A5E04 /* DNSSessionMatchType.swift */,
|
||||
54CA020D2426B2FC003A5E04 /* DNSFailRule.swift */,
|
||||
54CA020E2426B2FC003A5E04 /* AllRule.swift */,
|
||||
54CA020F2426B2FC003A5E04 /* DNSSessionMatchResult.swift */,
|
||||
54CA02102426B2FC003A5E04 /* Rule.swift */,
|
||||
54CA02112426B2FC003A5E04 /* DirectRule.swift */,
|
||||
54CA02122426B2FC003A5E04 /* RuleManager.swift */,
|
||||
54CA02132426B2FC003A5E04 /* IPRangeListRule.swift */,
|
||||
);
|
||||
path = Rule;
|
||||
sourceTree = "<group>";
|
||||
@@ -675,7 +773,6 @@
|
||||
54CA02302426B2FC003A5E04 /* EventType.swift */,
|
||||
54CA02312426B2FC003A5E04 /* ProxySocketEvent.swift */,
|
||||
54CA02322426B2FC003A5E04 /* TunnelEvent.swift */,
|
||||
54CA02332426B2FC003A5E04 /* RuleMatchEvent.swift */,
|
||||
);
|
||||
path = Event;
|
||||
sourceTree = "<group>";
|
||||
@@ -693,12 +790,8 @@
|
||||
54CA02362426B2FC003A5E04 /* AdapterSocket */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54CA02372426B2FC003A5E04 /* HTTPAdapter.swift */,
|
||||
54CA02382426B2FC003A5E04 /* SecureHTTPAdapter.swift */,
|
||||
54CA023A2426B2FC003A5E04 /* AdapterSocket.swift */,
|
||||
54CA023B2426B2FC003A5E04 /* DirectAdapter.swift */,
|
||||
54CA023C2426B2FC003A5E04 /* SOCKS5Adapter.swift */,
|
||||
54CA023D2426B2FC003A5E04 /* RejectAdapter.swift */,
|
||||
54CA023E2426B2FC003A5E04 /* Factory */,
|
||||
);
|
||||
path = AdapterSocket;
|
||||
@@ -707,14 +800,7 @@
|
||||
54CA023E2426B2FC003A5E04 /* Factory */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54CA02412426B2FC003A5E04 /* AuthenticationServerAdapterFactory.swift */,
|
||||
54CA02422426B2FC003A5E04 /* RejectAdapterFactory.swift */,
|
||||
54CA02432426B2FD003A5E04 /* AdapterFactory.swift */,
|
||||
54CA02442426B2FD003A5E04 /* SOCKS5AdapterFactory.swift */,
|
||||
54CA02452426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift */,
|
||||
54CA02462426B2FD003A5E04 /* ServerAdapterFactory.swift */,
|
||||
54CA02472426B2FD003A5E04 /* AdapterFactoryManager.swift */,
|
||||
54CA02482426B2FD003A5E04 /* HTTPAdapterFactory.swift */,
|
||||
);
|
||||
path = Factory;
|
||||
sourceTree = "<group>";
|
||||
@@ -723,9 +809,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54CA024F2426B2FD003A5E04 /* HTTPProxySocket.swift */,
|
||||
54CA02502426B2FD003A5E04 /* DirectProxySocket.swift */,
|
||||
54CA02512426B2FD003A5E04 /* ProxySocket.swift */,
|
||||
54CA02522426B2FD003A5E04 /* SOCKS5ProxySocket.swift */,
|
||||
);
|
||||
path = ProxySocket;
|
||||
sourceTree = "<group>";
|
||||
@@ -733,7 +817,7 @@
|
||||
54E540F0247C386500F7C34A /* Data Source */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54E540F3247D3F2600F7C34A /* TestDataSource.swift */,
|
||||
54E540F3247D3F2600F7C34A /* SimulatorVPN.swift */,
|
||||
54E540F92482414800F7C34A /* SyncUpdate.swift */,
|
||||
54D8B98524796E9800EB2414 /* GroupedDomainDataSource.swift */,
|
||||
54E540F1247C423200F7C34A /* DomainFilter.swift */,
|
||||
@@ -792,7 +876,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1130;
|
||||
LastUpgradeCheck = 1010;
|
||||
LastUpgradeCheck = 1200;
|
||||
ORGANIZATIONNAME = relikd;
|
||||
TargetAttributes = {
|
||||
541AC5D32399498A00A769D7 = {
|
||||
@@ -836,11 +920,32 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
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;
|
||||
@@ -849,6 +954,17 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
543078AD24B5E12500278F2D /* typewriter2.caf in Resources */,
|
||||
543078BB24B5E12500278F2D /* typewriter1.caf in Resources */,
|
||||
543078BF24B5E12500278F2D /* drum2.caf in Resources */,
|
||||
543078AF24B5E12500278F2D /* wood1.caf in Resources */,
|
||||
543078B124B5E12500278F2D /* plop2.caf in Resources */,
|
||||
543078AB24B5E12500278F2D /* snap2.caf in Resources */,
|
||||
543078B924B5E12500278F2D /* wood2.caf in Resources */,
|
||||
543078B724B5E12500278F2D /* drum1.caf in Resources */,
|
||||
543078B524B5E12500278F2D /* snap1.caf in Resources */,
|
||||
543078B324B5E12500278F2D /* plop1.caf in Resources */,
|
||||
543078BD24B5E12500278F2D /* clock.caf in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -862,18 +978,22 @@
|
||||
54E67E4924A8B1280025D261 /* Prefs.swift in Sources */,
|
||||
54E540F8247DB90F00F7C34A /* RecordingsDB.swift in Sources */,
|
||||
54E67E4F24A8E2910025D261 /* Equatable.swift in Sources */,
|
||||
54E540F4247D3F2600F7C34A /* TestDataSource.swift in Sources */,
|
||||
54E540F4247D3F2600F7C34A /* SimulatorVPN.swift in Sources */,
|
||||
541075D924CE2C7200D6F1BF /* GlassVPNHook.swift in Sources */,
|
||||
541075D524CE286200D6F1BF /* CachedConnectionAlert.swift in Sources */,
|
||||
5404AEEF24ACC089003B2F54 /* VCAnalysisBar.swift in Sources */,
|
||||
545DDDD424466D37003B6544 /* AutoLayout.swift in Sources */,
|
||||
54B345AD241BBB00004C53CC /* DBExtensions.swift in Sources */,
|
||||
54E540F2247C423200F7C34A /* DomainFilter.swift in Sources */,
|
||||
54D8B97E2471B88900EB2414 /* DBCommon.swift in Sources */,
|
||||
54D8B9832471BD8100EB2414 /* DBAppOnly.swift in Sources */,
|
||||
5412FCC224C628FA000DE429 /* TVCReminderAlerts.swift in Sources */,
|
||||
54E67E4D24A8E20D0025D261 /* TVCSettings.swift in Sources */,
|
||||
540E67842433FAFE00871BBE /* TVCPreviousRecords.swift in Sources */,
|
||||
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 */,
|
||||
@@ -881,15 +1001,20 @@
|
||||
54B345A9241BBA0B004C53CC /* Logging.swift in Sources */,
|
||||
54B34596240F0513004C53CC /* TableView.swift in Sources */,
|
||||
540E6780242D2CF100871BBE /* VCRecordings.swift in Sources */,
|
||||
541075D124CDBA0000D6F1BF /* ThrottledBatchQueue.swift in Sources */,
|
||||
54953E3323DC752E0054345C /* DBCore.swift in Sources */,
|
||||
54E67E4624A8B0FE0025D261 /* PrefsShared.swift in Sources */,
|
||||
5412FCC324C628FA000DE429 /* TVCChooseAlertTone.swift in Sources */,
|
||||
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 */,
|
||||
54D8B97C2471A7E000EB2414 /* String.swift in Sources */,
|
||||
54E67E5124A8E8820025D261 /* View.swift in Sources */,
|
||||
@@ -897,8 +1022,12 @@
|
||||
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 */,
|
||||
545DDDD124436983003B6544 /* QuickUI.swift in Sources */,
|
||||
541AC5D82399498A00A769D7 /* AppDelegate.swift in Sources */,
|
||||
541FC47624A12D01009154D8 /* IBViews.swift in Sources */,
|
||||
@@ -907,6 +1036,7 @@
|
||||
54D8B97A246C9F2000EB2414 /* FilterPipeline.swift in Sources */,
|
||||
54B34594240E6343004C53CC /* TVCFilter.swift in Sources */,
|
||||
549ECD9D24A7AD550097571C /* CustomAlert.swift in Sources */,
|
||||
54CE8BC424B1ED2100CC1756 /* PushNotification.swift in Sources */,
|
||||
54E540FA2482414800F7C34A /* SyncUpdate.swift in Sources */,
|
||||
5404AEED24A95F3F003B2F54 /* SlideInAnimation.swift in Sources */,
|
||||
);
|
||||
@@ -919,7 +1049,6 @@
|
||||
54CA027A2426B2FD003A5E04 /* HTTPURL.swift in Sources */,
|
||||
54CA025D2426B2FD003A5E04 /* HTTPHeader.swift in Sources */,
|
||||
54CA02832426B2FD003A5E04 /* DNSSessionMatchResult.swift in Sources */,
|
||||
54CA02862426B2FD003A5E04 /* RuleManager.swift in Sources */,
|
||||
54CA02B82426B2FD003A5E04 /* HTTPProxySocket.swift in Sources */,
|
||||
54CA02C32426DCCD003A5E04 /* GCDAsyncSocket.m in Sources */,
|
||||
54CA02752426B2FD003A5E04 /* IPRange.swift in Sources */,
|
||||
@@ -929,27 +1058,22 @@
|
||||
5404AEEB24A90717003B2F54 /* PrefsShared.swift in Sources */,
|
||||
54CA02C42426DCCD003A5E04 /* GCDAsyncUdpSocket.m in Sources */,
|
||||
54CA02BA2426B2FD003A5E04 /* ProxySocket.swift in Sources */,
|
||||
54CA025E2426B2FD003A5E04 /* ResponseGeneratorFactory.swift in Sources */,
|
||||
54CA02892426B2FD003A5E04 /* Tunnel.swift in Sources */,
|
||||
54CA029F2426B2FD003A5E04 /* ProxySocketEvent.swift in Sources */,
|
||||
54CA027D2426B2FD003A5E04 /* GlobalIntializer.swift in Sources */,
|
||||
54CA026F2426B2FD003A5E04 /* Port.swift in Sources */,
|
||||
54CA028A2426B2FD003A5E04 /* ResponseGenerator.swift in Sources */,
|
||||
54CA027C2426B2FD003A5E04 /* StreamScanner.swift in Sources */,
|
||||
54CA02AF2426B2FD003A5E04 /* SOCKS5AdapterFactory.swift in Sources */,
|
||||
54CA029E2426B2FD003A5E04 /* EventType.swift in Sources */,
|
||||
54CA02912426B2FD003A5E04 /* DNSMessage.swift in Sources */,
|
||||
54CA02712426B2FD003A5E04 /* UInt128.swift in Sources */,
|
||||
54CA02882426B2FD003A5E04 /* QueueFactory.swift in Sources */,
|
||||
54CA02A12426B2FD003A5E04 /* RuleMatchEvent.swift in Sources */,
|
||||
54CA02BE2426D4F3003A5E04 /* DDLog.swift in Sources */,
|
||||
54CA02962426B2FD003A5E04 /* PacketProtocolParser.swift in Sources */,
|
||||
54CA02932426B2FD003A5E04 /* DNSServer.swift in Sources */,
|
||||
54CA02B22426B2FD003A5E04 /* AdapterFactoryManager.swift in Sources */,
|
||||
54CA02AE2426B2FD003A5E04 /* AdapterFactory.swift in Sources */,
|
||||
54CA02A82426B2FD003A5E04 /* SOCKS5Adapter.swift in Sources */,
|
||||
54CA02792426B2FD003A5E04 /* Checksum.swift in Sources */,
|
||||
54CA02AD2426B2FD003A5E04 /* RejectAdapterFactory.swift in Sources */,
|
||||
541075D224CDBA0000D6F1BF /* ThrottledBatchQueue.swift in Sources */,
|
||||
54CA02672426B2FD003A5E04 /* RawTCPSocketProtocol.swift in Sources */,
|
||||
54CA02602426B2FD003A5E04 /* GCDProxyServer.swift in Sources */,
|
||||
54CA026B2426B2FD003A5E04 /* GCDTCPSocket.swift in Sources */,
|
||||
@@ -957,50 +1081,38 @@
|
||||
54CA01D52426B252003A5E04 /* SafeDict.swift in Sources */,
|
||||
54CA027B2426B2FD003A5E04 /* HTTPAuthentication.swift in Sources */,
|
||||
54CA02762426B2FD003A5E04 /* IPAddress.swift in Sources */,
|
||||
54CA02B02426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift in Sources */,
|
||||
54CA02A62426B2FD003A5E04 /* AdapterSocket.swift in Sources */,
|
||||
54CA02742426B2FD003A5E04 /* IPMask.swift in Sources */,
|
||||
54CA02BB2426B2FD003A5E04 /* SOCKS5ProxySocket.swift in Sources */,
|
||||
541075CF24C9D43A00D6F1BF /* UNNotification.swift in Sources */,
|
||||
54D8B97F2471B89100EB2414 /* DBCommon.swift in Sources */,
|
||||
54CA02A42426B2FD003A5E04 /* SecureHTTPAdapter.swift in Sources */,
|
||||
54CA02942426B2FD003A5E04 /* DNSResolver.swift in Sources */,
|
||||
54CA025F2426B2FD003A5E04 /* ProxyServer.swift in Sources */,
|
||||
54CA02842426B2FD003A5E04 /* Rule.swift in Sources */,
|
||||
54CA02B92426B2FD003A5E04 /* DirectProxySocket.swift in Sources */,
|
||||
54CE8BC524B1ED2100CC1756 /* PushNotification.swift in Sources */,
|
||||
54751E522423955100168273 /* URL.swift in Sources */,
|
||||
54CA02A92426B2FD003A5E04 /* RejectAdapter.swift in Sources */,
|
||||
54CA02732426B2FD003A5E04 /* IPPool.swift in Sources */,
|
||||
54CA027E2426B2FD003A5E04 /* DomainListRule.swift in Sources */,
|
||||
541075D624CE286200D6F1BF /* CachedConnectionAlert.swift in Sources */,
|
||||
54CA02782426B2FD003A5E04 /* BinaryDataScanner.swift in Sources */,
|
||||
54CA02B12426B2FD003A5E04 /* ServerAdapterFactory.swift in Sources */,
|
||||
54CA02952426B2FD003A5E04 /* DNSEnums.swift in Sources */,
|
||||
54CA02802426B2FD003A5E04 /* DNSSessionMatchType.swift in Sources */,
|
||||
54CA02A22426B2FD003A5E04 /* ObserverFactory.swift in Sources */,
|
||||
54CA02612426B2FD003A5E04 /* GCDSOCKS5ProxyServer.swift in Sources */,
|
||||
54CA029D2426B2FD003A5E04 /* ProxyServerEvent.swift in Sources */,
|
||||
54CA02BC2426B2FD003A5E04 /* SocketProtocol.swift in Sources */,
|
||||
54CA029C2426B2FD003A5E04 /* AdapterSocketEvent.swift in Sources */,
|
||||
54CA02A72426B2FD003A5E04 /* DirectAdapter.swift in Sources */,
|
||||
54CA02A32426B2FD003A5E04 /* HTTPAdapter.swift in Sources */,
|
||||
54CA02622426B2FD003A5E04 /* GCDHTTPProxyServer.swift in Sources */,
|
||||
54CA02822426B2FD003A5E04 /* AllRule.swift in Sources */,
|
||||
543CDB2023EEE61900B7F323 /* PacketTunnelProvider.swift in Sources */,
|
||||
54CA02662426B2FD003A5E04 /* NWUDPSocket.swift in Sources */,
|
||||
54CA02682426B2FD003A5E04 /* NWTCPSocket.swift in Sources */,
|
||||
54CA02852426B2FD003A5E04 /* DirectRule.swift in Sources */,
|
||||
54CA01D32426B23D003A5E04 /* Resolver.swift in Sources */,
|
||||
54CA028B2426B2FD003A5E04 /* Utils.swift in Sources */,
|
||||
54CA02972426B2FD003A5E04 /* IPPacket.swift in Sources */,
|
||||
54CA026A2426B2FD003A5E04 /* RawSocketFactory.swift in Sources */,
|
||||
54CA02A02426B2FD003A5E04 /* TunnelEvent.swift in Sources */,
|
||||
546063E523FEFAFE008F505A /* DBCore.swift in Sources */,
|
||||
54CA02872426B2FD003A5E04 /* IPRangeListRule.swift in Sources */,
|
||||
54CA02922426B2FD003A5E04 /* DNSSession.swift in Sources */,
|
||||
54CA026D2426B2FD003A5E04 /* Opt.swift in Sources */,
|
||||
54CA02B32426B2FD003A5E04 /* HTTPAdapterFactory.swift in Sources */,
|
||||
54CA02702426B2FD003A5E04 /* HTTPStreamScanner.swift in Sources */,
|
||||
54CA02812426B2FD003A5E04 /* DNSFailRule.swift in Sources */,
|
||||
54CA02AC2426B2FD003A5E04 /* AuthenticationServerAdapterFactory.swift in Sources */,
|
||||
541075DA24CE2C7200D6F1BF /* GlassVPNHook.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1031,6 +1143,38 @@
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
543078C124B60F3B00278F2D /* Settings.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
543078C224B60F3B00278F2D /* Base */,
|
||||
);
|
||||
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 */
|
||||
@@ -1060,6 +1204,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -1124,6 +1269,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@@ -1161,7 +1307,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 34;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1180,7 +1326,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 34;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1199,7 +1345,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 34;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
@@ -1217,7 +1363,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 34;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1130"
|
||||
LastUpgradeVersion = "1200"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1130"
|
||||
LastUpgradeVersion = "1200"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
|
||||
@@ -1,50 +1,8 @@
|
||||
import NetworkExtension
|
||||
|
||||
fileprivate var filterDomains: [String]!
|
||||
fileprivate var filterOptions: [(block: Bool, ignore: Bool)]!
|
||||
|
||||
|
||||
// MARK: Backward DNS Binary Tree Lookup
|
||||
|
||||
fileprivate func reloadDomainFilter() {
|
||||
let tmp = AppDB?.loadFilters()?.map({
|
||||
(String($0.reversed()), $1)
|
||||
}).sorted(by: { $0.0 < $1.0 }) ?? []
|
||||
filterDomains = tmp.map { $0.0 }
|
||||
filterOptions = tmp.map { ($1.contains(.blocked), $1.contains(.ignored)) }
|
||||
}
|
||||
|
||||
fileprivate func filterIndex(for domain: String) -> Int {
|
||||
let reverseDomain = String(domain.reversed())
|
||||
var lo = 0, hi = filterDomains.count - 1
|
||||
while lo <= hi {
|
||||
let mid = (lo + hi)/2
|
||||
if filterDomains[mid] < reverseDomain {
|
||||
lo = mid + 1
|
||||
} else if reverseDomain < filterDomains[mid] {
|
||||
hi = mid - 1
|
||||
} else {
|
||||
return mid
|
||||
}
|
||||
}
|
||||
if lo > 0, reverseDomain.hasPrefix(filterDomains[lo - 1] + ".") {
|
||||
return lo - 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
private let queue = DispatchQueue.init(label: "PSIGlassDNSQueue", qos: .userInteractive, target: .main)
|
||||
|
||||
private func logAsync(_ domain: String, blocked: Bool) {
|
||||
queue.async {
|
||||
do {
|
||||
try AppDB?.logWrite(domain, blocked: blocked)
|
||||
} catch {
|
||||
DDLogWarn("Couldn't write: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let connectMessage: Data = "CONNECT".data(using: .ascii)!
|
||||
let swcdUserAgent: Data = "User-Agent: swcd".data(using: .ascii)!
|
||||
fileprivate var hook : GlassVPNHook!
|
||||
|
||||
// MARK: ObserverFactory
|
||||
|
||||
@@ -59,14 +17,17 @@ class LDObserverFactory: ObserverFactory {
|
||||
override func signal(_ event: ProxySocketEvent) {
|
||||
switch event {
|
||||
case .receivedRequest(let session, let socket):
|
||||
let i = filterIndex(for: session.host)
|
||||
if i >= 0 {
|
||||
let (block, ignore) = filterOptions[i]
|
||||
if !ignore { logAsync(session.host, blocked: block) }
|
||||
if block { socket.forceDisconnect() }
|
||||
var kill = !hook.isBackgroundRecording && hook.forceDisconnectUnresolvable && session.ipAddress.isEmpty
|
||||
if kill || socket.isCancelled { // isCancelled is set by branch below
|
||||
hook.silentlyPrevented(session.host)
|
||||
} else {
|
||||
// TODO: disable filter during recordings
|
||||
logAsync(session.host, blocked: false)
|
||||
kill = hook.processDNSRequest(session.host)
|
||||
}
|
||||
if kill { socket.forceDisconnect() }
|
||||
case .readData(let data, on: let socket):
|
||||
if !hook.isBackgroundRecording, hook.forceDisconnectSWCD,
|
||||
data.starts(with: connectMessage), data.range(of: swcdUserAgent) != nil {
|
||||
socket.disconnect() // sets isCancelled above
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -84,29 +45,59 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
private let proxyServerAddress = "127.0.0.1"
|
||||
private var proxyServer: GCDHTTPProxyServer!
|
||||
|
||||
private var autoDeleteTimer: Timer? = nil
|
||||
|
||||
private func reloadSettings() {
|
||||
reloadDomainFilter()
|
||||
setAutoDelete(PrefsShared.AutoDeleteLogsDays)
|
||||
}
|
||||
// MARK: Delegate
|
||||
|
||||
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||
DDLogVerbose("startTunnel with with options: \(String(describing: options))")
|
||||
PrefsShared.registerDefaults()
|
||||
do {
|
||||
try SQLiteDatabase.open().initCommonScheme()
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
completionHandler(error) // if we cant open db, fail immediately
|
||||
return
|
||||
}
|
||||
reloadSettings()
|
||||
|
||||
if proxyServer != nil {
|
||||
proxyServer.stop()
|
||||
}
|
||||
// stop previous if any
|
||||
if proxyServer != nil { proxyServer.stop() }
|
||||
proxyServer = nil
|
||||
|
||||
// Create proxy
|
||||
willInitProxy()
|
||||
|
||||
self.setTunnelNetworkSettings(createProxy()) { error in
|
||||
guard error == nil else {
|
||||
DDLogError("setTunnelNetworkSettings error: \(error!)")
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
self.proxyServer = GCDHTTPProxyServer(address: IPAddress(fromString: self.proxyServerAddress), port: Port(port: self.proxyServerPort))
|
||||
do {
|
||||
try self.proxyServer.start()
|
||||
self.didInitProxy()
|
||||
completionHandler(nil)
|
||||
} catch let proxyError {
|
||||
DDLogError("Error starting proxy server \(proxyError)")
|
||||
completionHandler(proxyError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
DDLogVerbose("stopTunnel with reason: \(reason)")
|
||||
shutdown()
|
||||
completionHandler()
|
||||
exit(EXIT_SUCCESS)
|
||||
}
|
||||
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
||||
hook.handleAppMessage(messageData)
|
||||
}
|
||||
|
||||
// MARK: Helper
|
||||
|
||||
private func willInitProxy() {
|
||||
hook = GlassVPNHook()
|
||||
}
|
||||
|
||||
private func createProxy() -> NEPacketTunnelNetworkSettings {
|
||||
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: proxyServerAddress)
|
||||
settings.mtu = NSNumber(value: 1500)
|
||||
|
||||
@@ -123,93 +114,29 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
settings.proxySettings = proxySettings;
|
||||
RawSocketFactory.TunnelProvider = self
|
||||
ObserverFactory.currentFactory = LDObserverFactory()
|
||||
|
||||
self.setTunnelNetworkSettings(settings) { error in
|
||||
guard error == nil else {
|
||||
DDLogError("setTunnelNetworkSettings error: \(String(describing: error))")
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
completionHandler(nil)
|
||||
|
||||
self.proxyServer = GCDHTTPProxyServer(address: IPAddress(fromString: self.proxyServerAddress), port: Port(port: self.proxyServerPort))
|
||||
do {
|
||||
try self.proxyServer.start()
|
||||
completionHandler(nil)
|
||||
}
|
||||
catch let proxyError {
|
||||
DDLogError("Error starting proxy server \(proxyError)")
|
||||
completionHandler(proxyError)
|
||||
}
|
||||
return settings
|
||||
}
|
||||
|
||||
private func didInitProxy() {
|
||||
if PrefsShared.RestartReminder.Enabled {
|
||||
PushNotification.scheduleRestartReminderBadge(on: false)
|
||||
PushNotification.cancel(.CantStopMeNowReminder)
|
||||
}
|
||||
}
|
||||
|
||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
DDLogVerbose("stopTunnel with reason: \(reason)")
|
||||
private func shutdown() {
|
||||
// proxy
|
||||
DNSServer.currentServer = nil
|
||||
RawSocketFactory.TunnelProvider = nil
|
||||
ObserverFactory.currentFactory = nil
|
||||
proxyServer.stop()
|
||||
proxyServer = nil
|
||||
filterDomains = nil
|
||||
filterOptions = nil
|
||||
autoDeleteTimer?.fire() // one last time before we quit
|
||||
autoDeleteTimer?.invalidate()
|
||||
completionHandler()
|
||||
exit(EXIT_SUCCESS)
|
||||
}
|
||||
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
|
||||
let message = String(data: messageData, encoding: .utf8)
|
||||
if let msg = message, let i = msg.firstIndex(of: ":") {
|
||||
let action = msg.prefix(upTo: i)
|
||||
let value = msg.suffix(from: msg.index(after: i))
|
||||
switch action {
|
||||
case "filter-update":
|
||||
reloadDomainFilter() // TODO: reload only selected domain?
|
||||
return
|
||||
case "auto-delete":
|
||||
setAutoDelete(Int(value) ?? PrefsShared.AutoDeleteLogsDays)
|
||||
return
|
||||
default: break
|
||||
}
|
||||
}
|
||||
DDLogWarn("This should never happen! Received unknown handleAppMessage: \(message ?? messageData.base64EncodedString())")
|
||||
reloadSettings() // just in case we fallback to do everything
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ################################################################
|
||||
// #
|
||||
// # MARK: - Auto-delete Timer
|
||||
// #
|
||||
// ################################################################
|
||||
|
||||
extension PacketTunnelProvider {
|
||||
|
||||
private func setAutoDelete(_ days: Int) {
|
||||
autoDeleteTimer?.invalidate()
|
||||
guard days > 0 else { return }
|
||||
// Repeat interval uses days as hours. min 1 hr, max 24 hrs.
|
||||
let interval = TimeInterval(min(24, days) * 60 * 60)
|
||||
autoDeleteTimer = Timer.scheduledTimer(timeInterval: interval,
|
||||
target: self, selector: #selector(autoDeleteNow),
|
||||
userInfo: days, repeats: true)
|
||||
autoDeleteTimer!.fire()
|
||||
}
|
||||
|
||||
@objc private func autoDeleteNow(_ sender: Timer) {
|
||||
DDLogInfo("Auto-delete old logs")
|
||||
queue.async {
|
||||
do {
|
||||
try AppDB?.dnsLogsDeleteOlderThan(days: sender.userInfo as! Int)
|
||||
} catch {
|
||||
DDLogWarn("Couldn't delete logs, will retry in 5 minutes. \(error)")
|
||||
if sender.isValid {
|
||||
sender.fireDate = Date().addingTimeInterval(300) // retry in 5 min
|
||||
}
|
||||
}
|
||||
// custom
|
||||
hook.cleanUp()
|
||||
hook = nil
|
||||
if PrefsShared.RestartReminder.Enabled {
|
||||
PushNotification.scheduleRestartReminderBadge(on: true)
|
||||
PushNotification.scheduleRestartReminderBanner()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
GlassVPN/SwiftSocket/.DS_Store
vendored
Normal file
@@ -1,16 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
public enum RuleMatchEvent: EventType {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .ruleMatched(session, rule: rule):
|
||||
return "Rule \(rule) matched session \(session)."
|
||||
case let .ruleDidNotMatch(session, rule: rule):
|
||||
return "Rule \(rule) did not match session \(session)."
|
||||
case let .dnsRuleMatched(session, rule: rule, type: type, result: result):
|
||||
return "Rule \(rule) matched DNS session \(session) of type \(type), the result is \(result)."
|
||||
}
|
||||
}
|
||||
|
||||
case ruleMatched(ConnectSession, rule: Rule), ruleDidNotMatch(ConnectSession, rule: Rule), dnsRuleMatched(DNSSession, rule: Rule, type: DNSSessionMatchType, result: DNSSessionMatchResult)
|
||||
}
|
||||
@@ -20,8 +20,4 @@ open class ObserverFactory {
|
||||
open func getObserverForProxyServer(_ server: ProxyServer) -> Observer<ProxyServerEvent>? {
|
||||
return nil
|
||||
}
|
||||
|
||||
open func getObserverForRuleManager(_ manager: RuleManager) -> Observer<RuleMatchEvent>? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,6 @@ open class DNSServer: DNSResolverDelegate, IPStackProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
RuleManager.currentManager.matchDNS(session, type: .domain)
|
||||
|
||||
switch session.matchResult! {
|
||||
case .fake:
|
||||
guard setUpFakeIP(session) else {
|
||||
@@ -248,10 +246,6 @@ open class DNSServer: DNSResolverDelegate, IPStackProtocol {
|
||||
|
||||
session.realIP = message.resolvedIPv4Address
|
||||
|
||||
if session.matchResult != .fake && session.matchResult != .real {
|
||||
RuleManager.currentManager.matchDNS(session, type: .ip)
|
||||
}
|
||||
|
||||
switch session.matchResult! {
|
||||
case .fake:
|
||||
if !self.setUpFakeIP(session) {
|
||||
|
||||
@@ -7,7 +7,6 @@ open class DNSSession {
|
||||
open var fakeIP: IPAddress?
|
||||
open var realResponseMessage: DNSMessage?
|
||||
var realResponseIPPacket: IPPacket?
|
||||
open var matchedRule: Rule?
|
||||
open var matchResult: DNSSessionMatchResult?
|
||||
var indexToMatch = 0
|
||||
var expireAt: Date?
|
||||
|
||||
@@ -21,9 +21,6 @@ public final class ConnectSession {
|
||||
/// The requested port.
|
||||
public let port: Int
|
||||
|
||||
/// The rule to use to connect to remote.
|
||||
public var matchedRule: Rule?
|
||||
|
||||
/// Whether If the `requestedHost` is an IP address.
|
||||
public let fakeIPEnabled: Bool
|
||||
|
||||
@@ -126,11 +123,6 @@ public final class ConnectSession {
|
||||
|
||||
host = session.requestMessage.queries[0].name
|
||||
ipAddress = session.realIP?.presentation ?? ""
|
||||
matchedRule = session.matchedRule
|
||||
|
||||
// if session.countryCode != nil {
|
||||
// country = session.countryCode!
|
||||
// }
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ open class HTTPHeader {
|
||||
// Chunk is not supported yet.
|
||||
open var contentLength: Int = 0
|
||||
open var headers: [(String, String)] = []
|
||||
open var rawHeader: Data?
|
||||
|
||||
public init(headerString: String) throws {
|
||||
let lines = headerString.components(separatedBy: "\r\n")
|
||||
@@ -127,7 +126,6 @@ open class HTTPHeader {
|
||||
}
|
||||
|
||||
try self.init(headerString: headerString)
|
||||
rawHeader = headerData
|
||||
}
|
||||
|
||||
open subscript(index: String) -> String? {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// The SOCKS5 proxy server.
|
||||
public final class GCDSOCKS5ProxyServer: GCDProxyServer {
|
||||
/**
|
||||
Create an instance of SOCKS5 proxy server.
|
||||
|
||||
- parameter address: The address of proxy server.
|
||||
- parameter port: The port of proxy server.
|
||||
*/
|
||||
override public init(address: IPAddress?, port: Port) {
|
||||
super.init(address: address, port: port)
|
||||
}
|
||||
|
||||
/**
|
||||
Handle the new accepted socket as a SOCKS5 proxy connection.
|
||||
|
||||
- parameter socket: The accepted socket.
|
||||
*/
|
||||
override public func handleNewGCDSocket(_ socket: GCDTCPSocket) {
|
||||
let proxySocket = SOCKS5ProxySocket(socket: socket)
|
||||
didAcceptNewSocket(proxySocket)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
open class ResponseGeneratorFactory {
|
||||
static var HTTPProxyResponseGenerator: ResponseGenerator.Type?
|
||||
static var SOCKS5ProxyResponseGenerator: ResponseGenerator.Type?
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// The rule matches all DNS and connect sessions.
|
||||
open class AllRule: Rule {
|
||||
fileprivate let adapterFactory: AdapterFactory
|
||||
|
||||
open override var description: String {
|
||||
return "<AllRule>"
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new `AllRule` instance.
|
||||
|
||||
- parameter adapterFactory: The factory which builds a corresponding adapter when needed.
|
||||
*/
|
||||
public init(adapterFactory: AdapterFactory) {
|
||||
self.adapterFactory = adapterFactory
|
||||
super.init()
|
||||
}
|
||||
|
||||
/**
|
||||
Match DNS session to this rule.
|
||||
|
||||
- parameter session: The DNS session to match.
|
||||
- parameter type: What kind of information is available.
|
||||
|
||||
- returns: The result of match.
|
||||
*/
|
||||
override open func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) -> DNSSessionMatchResult {
|
||||
// only return real IP when we connect to remote directly
|
||||
if let _ = adapterFactory as? DirectAdapterFactory {
|
||||
return .real
|
||||
} else {
|
||||
return .fake
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Match connect session to this rule.
|
||||
|
||||
- parameter session: connect session to match.
|
||||
|
||||
- returns: The configured adapter.
|
||||
*/
|
||||
override open func match(_ session: ConnectSession) -> AdapterFactory? {
|
||||
return adapterFactory
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// The rule matches the request which failed to look up.
|
||||
open class DNSFailRule: Rule {
|
||||
fileprivate let adapterFactory: AdapterFactory
|
||||
|
||||
open override var description: String {
|
||||
return "<DNSFailRule>"
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new `DNSFailRule` instance.
|
||||
|
||||
- parameter adapterFactory: The factory which builds a corresponding adapter when needed.
|
||||
*/
|
||||
public init(adapterFactory: AdapterFactory) {
|
||||
self.adapterFactory = adapterFactory
|
||||
super.init()
|
||||
}
|
||||
|
||||
/**
|
||||
Match DNS request to this rule.
|
||||
|
||||
- parameter session: The DNS session to match.
|
||||
- parameter type: What kind of information is available.
|
||||
|
||||
- returns: The result of match.
|
||||
*/
|
||||
override open func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) -> DNSSessionMatchResult {
|
||||
guard type == .ip else {
|
||||
return .unknown
|
||||
}
|
||||
|
||||
// only return real IP when we connect to remote directly
|
||||
if session.realIP == nil {
|
||||
if let _ = adapterFactory as? DirectAdapterFactory {
|
||||
return .real
|
||||
} else {
|
||||
return .fake
|
||||
}
|
||||
} else {
|
||||
return .pass
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Match connect session to this rule.
|
||||
|
||||
- parameter session: connect session to match.
|
||||
|
||||
- returns: The configured adapter.
|
||||
*/
|
||||
override open func match(_ session: ConnectSession) -> AdapterFactory? {
|
||||
if session.ipAddress == "" {
|
||||
return adapterFactory
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// The rule matches every request and returns direct adapter.
|
||||
///
|
||||
/// This is equivalent to create an `AllRule` with a `DirectAdapterFactory`.
|
||||
open class DirectRule: AllRule {
|
||||
open override var description: String {
|
||||
return "<DirectRule>"
|
||||
}
|
||||
/**
|
||||
Create a new `DirectRule` instance.
|
||||
*/
|
||||
public init() {
|
||||
super.init(adapterFactory: DirectAdapterFactory())
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// The rule matches the host domain to a list of predefined criteria.
|
||||
open class DomainListRule: Rule {
|
||||
public enum MatchCriterion {
|
||||
case regex(NSRegularExpression), prefix(String), suffix(String), keyword(String), complete(String)
|
||||
|
||||
func match(_ domain: String) -> Bool {
|
||||
switch self {
|
||||
case .regex(let regex):
|
||||
return regex.firstMatch(in: domain, options: [], range: NSRange(location: 0, length: domain.utf8.count)) != nil
|
||||
case .prefix(let prefix):
|
||||
return domain.hasPrefix(prefix)
|
||||
case .suffix(let suffix):
|
||||
return domain.hasSuffix(suffix)
|
||||
case .keyword(let keyword):
|
||||
return domain.contains(keyword)
|
||||
case .complete(let match):
|
||||
return domain == match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let adapterFactory: AdapterFactory
|
||||
|
||||
open override var description: String {
|
||||
return "<DomainListRule>"
|
||||
}
|
||||
|
||||
/// The list of criteria to match to.
|
||||
open var matchCriteria: [MatchCriterion] = []
|
||||
|
||||
/**
|
||||
Create a new `DomainListRule` instance.
|
||||
|
||||
- parameter adapterFactory: The factory which builds a corresponding adapter when needed.
|
||||
- parameter criteria: The list of criteria to match.
|
||||
*/
|
||||
public init(adapterFactory: AdapterFactory, criteria: [MatchCriterion]) {
|
||||
self.adapterFactory = adapterFactory
|
||||
self.matchCriteria = criteria
|
||||
}
|
||||
|
||||
/**
|
||||
Match DNS request to this rule.
|
||||
|
||||
- parameter session: The DNS session to match.
|
||||
- parameter type: What kind of information is available.
|
||||
|
||||
- returns: The result of match.
|
||||
*/
|
||||
override open func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) -> DNSSessionMatchResult {
|
||||
if matchDomain(session.requestMessage.queries.first!.name) {
|
||||
if let _ = adapterFactory as? DirectAdapterFactory {
|
||||
return .real
|
||||
}
|
||||
return .fake
|
||||
}
|
||||
return .pass
|
||||
}
|
||||
|
||||
/**
|
||||
Match connect session to this rule.
|
||||
|
||||
- parameter session: connect session to match.
|
||||
|
||||
- returns: The configured adapter if matched, return `nil` if not matched.
|
||||
*/
|
||||
override open func match(_ session: ConnectSession) -> AdapterFactory? {
|
||||
if matchDomain(session.host) {
|
||||
return adapterFactory
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate func matchDomain(_ domain: String) -> Bool {
|
||||
for criterion in matchCriteria {
|
||||
if criterion.match(domain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// The rule matches the ip of the target hsot to a list of IP ranges.
|
||||
open class IPRangeListRule: Rule {
|
||||
fileprivate let adapterFactory: AdapterFactory
|
||||
|
||||
open override var description: String {
|
||||
return "<IPRangeList>"
|
||||
}
|
||||
|
||||
/// The list of regular expressions to match to.
|
||||
open var ranges: [IPRange] = []
|
||||
|
||||
/**
|
||||
Create a new `IPRangeListRule` instance.
|
||||
|
||||
- parameter adapterFactory: The factory which builds a corresponding adapter when needed.
|
||||
- parameter ranges: The list of IP ranges to match. The IP ranges are expressed in CIDR form ("127.0.0.1/8") or range form ("127.0.0.1+16777216").
|
||||
|
||||
- throws: The error when parsing the IP range.
|
||||
*/
|
||||
public init(adapterFactory: AdapterFactory, ranges: [String]) throws {
|
||||
self.adapterFactory = adapterFactory
|
||||
self.ranges = try ranges.map {
|
||||
let range = try IPRange(withString: $0)
|
||||
return range
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Match DNS request to this rule.
|
||||
|
||||
- parameter session: The DNS session to match.
|
||||
- parameter type: What kind of information is available.
|
||||
|
||||
- returns: The result of match.
|
||||
*/
|
||||
override open func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) -> DNSSessionMatchResult {
|
||||
guard type == .ip else {
|
||||
return .unknown
|
||||
}
|
||||
|
||||
// Probably we should match all answers?
|
||||
guard let ip = session.realIP else {
|
||||
return .pass
|
||||
}
|
||||
|
||||
for range in ranges {
|
||||
if range.contains(ip: ip) {
|
||||
return .fake
|
||||
}
|
||||
}
|
||||
return .pass
|
||||
}
|
||||
|
||||
/**
|
||||
Match connect session to this rule.
|
||||
|
||||
- parameter session: connect session to match.
|
||||
|
||||
- returns: The configured adapter if matched, return `nil` if not matched.
|
||||
*/
|
||||
override open func match(_ session: ConnectSession) -> AdapterFactory? {
|
||||
guard let ip = IPAddress(fromString: session.ipAddress) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
for range in ranges {
|
||||
if range.contains(ip: ip) {
|
||||
return adapterFactory
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// The rule defines what to do for DNS requests and connect sessions.
|
||||
open class Rule: CustomStringConvertible {
|
||||
open var description: String {
|
||||
return "<Rule>"
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new rule.
|
||||
*/
|
||||
public init() {
|
||||
}
|
||||
|
||||
/**
|
||||
Match DNS request to this rule.
|
||||
|
||||
- parameter session: The DNS session to match.
|
||||
- parameter type: What kind of information is available.
|
||||
|
||||
- returns: The result of match.
|
||||
*/
|
||||
open func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) -> DNSSessionMatchResult {
|
||||
return .real
|
||||
}
|
||||
|
||||
/**
|
||||
Match connect session to this rule.
|
||||
|
||||
- parameter session: connect session to match.
|
||||
|
||||
- returns: The configured adapter if matched, return `nil` if not matched.
|
||||
*/
|
||||
open func match(_ session: ConnectSession) -> AdapterFactory? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// The class managing rules.
|
||||
open class RuleManager {
|
||||
/// The current used `RuleManager`, there is only one manager should be used at a time.
|
||||
///
|
||||
/// - note: This should be set before any DNS or connect sessions.
|
||||
public static var currentManager: RuleManager = RuleManager(fromRules: [], appendDirect: true)
|
||||
|
||||
/// The rule list.
|
||||
var rules: [Rule] = []
|
||||
|
||||
open var observer: Observer<RuleMatchEvent>?
|
||||
|
||||
/**
|
||||
Create a new `RuleManager` from the given rules.
|
||||
|
||||
- parameter rules: The rules.
|
||||
- parameter appendDirect: Whether to append a `DirectRule` at the end of the list so any request does not match with any rule go directly.
|
||||
*/
|
||||
public init(fromRules rules: [Rule], appendDirect: Bool = false) {
|
||||
self.rules = []
|
||||
|
||||
if appendDirect || self.rules.count == 0 {
|
||||
self.rules.append(DirectRule())
|
||||
}
|
||||
|
||||
observer = ObserverFactory.currentFactory?.getObserverForRuleManager(self)
|
||||
}
|
||||
|
||||
/**
|
||||
Match DNS request to all rules.
|
||||
|
||||
- parameter session: The DNS session to match.
|
||||
- parameter type: What kind of information is available.
|
||||
*/
|
||||
func matchDNS(_ session: DNSSession, type: DNSSessionMatchType) {
|
||||
for (i, rule) in rules[session.indexToMatch..<rules.count].enumerated() {
|
||||
let result = rule.matchDNS(session, type: type)
|
||||
|
||||
observer?.signal(.dnsRuleMatched(session, rule: rule, type: type, result: result))
|
||||
|
||||
switch result {
|
||||
case .fake, .real, .unknown:
|
||||
session.matchedRule = rule
|
||||
session.matchResult = result
|
||||
session.indexToMatch = i + session.indexToMatch // add the offset
|
||||
return
|
||||
case .pass:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Match connect session to all rules.
|
||||
|
||||
- parameter session: connect session to match.
|
||||
|
||||
- returns: The matched configured adapter.
|
||||
*/
|
||||
func match(_ session: ConnectSession) -> AdapterFactory! {
|
||||
if session.matchedRule != nil {
|
||||
observer?.signal(.ruleMatched(session, rule: session.matchedRule!))
|
||||
return session.matchedRule!.match(session)
|
||||
}
|
||||
|
||||
for rule in rules {
|
||||
if let adapterFactory = rule.match(session) {
|
||||
observer?.signal(.ruleMatched(session, rule: rule))
|
||||
|
||||
session.matchedRule = rule
|
||||
return adapterFactory
|
||||
} else {
|
||||
observer?.signal(.ruleDidNotMatch(session, rule: rule))
|
||||
}
|
||||
}
|
||||
return nil // this should never happens
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// This is a very simple wrapper of a dict of type `[String: AdapterFactory]`.
|
||||
///
|
||||
/// Use it as a normal dict.
|
||||
public class AdapterFactoryManager {
|
||||
private var factoryDict: [String: AdapterFactory]
|
||||
|
||||
public subscript(index: String) -> AdapterFactory? {
|
||||
get {
|
||||
if index == "direct" {
|
||||
return DirectAdapterFactory()
|
||||
}
|
||||
return factoryDict[index]
|
||||
}
|
||||
set { factoryDict[index] = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
Initialize a new factory manager.
|
||||
|
||||
- parameter factoryDict: The factory dict.
|
||||
*/
|
||||
public init(factoryDict: [String: AdapterFactory]) {
|
||||
self.factoryDict = factoryDict
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// Factory building server adapter which requires authentication.
|
||||
open class HTTPAuthenticationAdapterFactory: ServerAdapterFactory {
|
||||
let auth: HTTPAuthentication?
|
||||
|
||||
required public init(serverHost: String, serverPort: Int, auth: HTTPAuthentication?) {
|
||||
self.auth = auth
|
||||
super.init(serverHost: serverHost, serverPort: serverPort)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// Factory building HTTP adapter.
|
||||
open class HTTPAdapterFactory: HTTPAuthenticationAdapterFactory {
|
||||
required public init(serverHost: String, serverPort: Int, auth: HTTPAuthentication?) {
|
||||
super.init(serverHost: serverHost, serverPort: serverPort, auth: auth)
|
||||
}
|
||||
|
||||
/**
|
||||
Get a HTTP adapter.
|
||||
|
||||
- parameter session: The connect session.
|
||||
|
||||
- returns: The built adapter.
|
||||
*/
|
||||
override open func getAdapterFor(session: ConnectSession) -> AdapterSocket {
|
||||
let adapter = HTTPAdapter(serverHost: serverHost, serverPort: serverPort, auth: auth)
|
||||
adapter.socket = RawSocketFactory.getRawSocket()
|
||||
return adapter
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
open class RejectAdapterFactory: AdapterFactory {
|
||||
public let delay: Int
|
||||
|
||||
public init(delay: Int = Opt.RejectAdapterDefaultDelay) {
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
override open func getAdapterFor(session: ConnectSession) -> AdapterSocket {
|
||||
return RejectAdapter(delay: delay)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// Factory building SOCKS5 adapter.
|
||||
open class SOCKS5AdapterFactory: ServerAdapterFactory {
|
||||
override public init(serverHost: String, serverPort: Int) {
|
||||
super.init(serverHost: serverHost, serverPort: serverPort)
|
||||
}
|
||||
|
||||
/**
|
||||
Get a SOCKS5 adapter.
|
||||
|
||||
- parameter session: The connect session.
|
||||
|
||||
- returns: The built adapter.
|
||||
*/
|
||||
override open func getAdapterFor(session: ConnectSession) -> AdapterSocket {
|
||||
let adapter = SOCKS5Adapter(serverHost: serverHost, serverPort: serverPort)
|
||||
adapter.socket = RawSocketFactory.getRawSocket()
|
||||
return adapter
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// Factory building secured HTTP (HTTP with SSL) adapter.
|
||||
open class SecureHTTPAdapterFactory: HTTPAdapterFactory {
|
||||
required public init(serverHost: String, serverPort: Int, auth: HTTPAuthentication?) {
|
||||
super.init(serverHost: serverHost, serverPort: serverPort, auth: auth)
|
||||
}
|
||||
|
||||
/**
|
||||
Get a secured HTTP adapter.
|
||||
|
||||
- parameter session: The connect session.
|
||||
|
||||
- returns: The built adapter.
|
||||
*/
|
||||
override open func getAdapterFor(session: ConnectSession) -> AdapterSocket {
|
||||
let adapter = SecureHTTPAdapter(serverHost: serverHost, serverPort: serverPort, auth: auth)
|
||||
adapter.socket = RawSocketFactory.getRawSocket()
|
||||
return adapter
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// Factory building adapter with proxy server host and port.
|
||||
open class ServerAdapterFactory: AdapterFactory {
|
||||
let serverHost: String
|
||||
let serverPort: Int
|
||||
|
||||
public init(serverHost: String, serverPort: Int) {
|
||||
self.serverHost = serverHost
|
||||
self.serverPort = serverPort
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
public enum HTTPAdapterError: Error, CustomStringConvertible {
|
||||
case invalidURL, serailizationFailure
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .invalidURL:
|
||||
return "Invalid url when connecting through proxy"
|
||||
case .serailizationFailure:
|
||||
return "Failed to serialize HTTP CONNECT header"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This adapter connects to remote host through a HTTP proxy.
|
||||
public class HTTPAdapter: AdapterSocket {
|
||||
enum HTTPAdapterStatus {
|
||||
case invalid,
|
||||
connecting,
|
||||
readingResponse,
|
||||
forwarding,
|
||||
stopped
|
||||
}
|
||||
|
||||
/// The host domain of the HTTP proxy.
|
||||
let serverHost: String
|
||||
|
||||
/// The port of the HTTP proxy.
|
||||
let serverPort: Int
|
||||
|
||||
/// The authentication information for the HTTP proxy.
|
||||
let auth: HTTPAuthentication?
|
||||
|
||||
/// Whether the connection to the proxy should be secured or not.
|
||||
var secured: Bool
|
||||
|
||||
var internalStatus: HTTPAdapterStatus = .invalid
|
||||
|
||||
public init(serverHost: String, serverPort: Int, auth: HTTPAuthentication?) {
|
||||
self.serverHost = serverHost
|
||||
self.serverPort = serverPort
|
||||
self.auth = auth
|
||||
secured = false
|
||||
super.init()
|
||||
}
|
||||
|
||||
override public func openSocketWith(session: ConnectSession) {
|
||||
super.openSocketWith(session: session)
|
||||
|
||||
guard !isCancelled else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
internalStatus = .connecting
|
||||
try socket.connectTo(host: serverHost, port: serverPort, enableTLS: secured, tlsSettings: nil)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
override public func didConnectWith(socket: RawTCPSocketProtocol) {
|
||||
super.didConnectWith(socket: socket)
|
||||
|
||||
guard let url = URL(string: "\(session.host):\(session.port)") else {
|
||||
observer?.signal(.errorOccured(HTTPAdapterError.invalidURL, on: self))
|
||||
disconnect()
|
||||
return
|
||||
}
|
||||
let message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "CONNECT" as CFString, url as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
|
||||
if let authData = auth {
|
||||
CFHTTPMessageSetHeaderFieldValue(message, "Proxy-Authorization" as CFString, authData.authString() as CFString?)
|
||||
}
|
||||
CFHTTPMessageSetHeaderFieldValue(message, "Host" as CFString, "\(session.host):\(session.port)" as CFString?)
|
||||
CFHTTPMessageSetHeaderFieldValue(message, "Content-Length" as CFString, "0" as CFString?)
|
||||
|
||||
guard let requestData = CFHTTPMessageCopySerializedMessage(message)?.takeRetainedValue() else {
|
||||
observer?.signal(.errorOccured(HTTPAdapterError.serailizationFailure, on: self))
|
||||
disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
internalStatus = .readingResponse
|
||||
write(data: requestData as Data)
|
||||
socket.readDataTo(data: Utils.HTTPData.DoubleCRLF)
|
||||
}
|
||||
|
||||
override public func didRead(data: Data, from socket: RawTCPSocketProtocol) {
|
||||
super.didRead(data: data, from: socket)
|
||||
|
||||
switch internalStatus {
|
||||
case .readingResponse:
|
||||
internalStatus = .forwarding
|
||||
observer?.signal(.readyForForward(self))
|
||||
delegate?.didBecomeReadyToForwardWith(socket: self)
|
||||
case .forwarding:
|
||||
observer?.signal(.readData(data, on: self))
|
||||
delegate?.didRead(data: data, from: self)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override public func didWrite(data: Data?, by socket: RawTCPSocketProtocol) {
|
||||
super.didWrite(data: data, by: socket)
|
||||
if internalStatus == .forwarding {
|
||||
observer?.signal(.wroteData(data, on: self))
|
||||
delegate?.didWrite(data: data, by: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
public class RejectAdapter: AdapterSocket {
|
||||
public let delay: Int
|
||||
|
||||
public init(delay: Int) {
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
override public func openSocketWith(session: ConnectSession) {
|
||||
super.openSocketWith(session: session)
|
||||
|
||||
QueueFactory.getQueue().asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(delay)) {
|
||||
[weak self] in
|
||||
self?.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Disconnect the socket elegantly.
|
||||
*/
|
||||
public override func disconnect(becauseOf error: Error? = nil) {
|
||||
guard !isCancelled else {
|
||||
return
|
||||
}
|
||||
|
||||
_cancelled = true
|
||||
session.disconnected(becauseOf: error, by: .adapter)
|
||||
observer?.signal(.disconnectCalled(self))
|
||||
_status = .closed
|
||||
delegate?.didDisconnectWith(socket: self)
|
||||
}
|
||||
|
||||
/**
|
||||
Disconnect the socket immediately.
|
||||
*/
|
||||
public override func forceDisconnect(becauseOf error: Error? = nil) {
|
||||
guard !isCancelled else {
|
||||
return
|
||||
}
|
||||
|
||||
_cancelled = true
|
||||
session.disconnected(becauseOf: error, by: .adapter)
|
||||
observer?.signal(.forceDisconnectCalled(self))
|
||||
_status = .closed
|
||||
delegate?.didDisconnectWith(socket: self)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
public class SOCKS5Adapter: AdapterSocket {
|
||||
enum SOCKS5AdapterStatus {
|
||||
case invalid,
|
||||
connecting,
|
||||
readingMethodResponse,
|
||||
readingResponseFirstPart,
|
||||
readingResponseSecondPart,
|
||||
forwarding
|
||||
}
|
||||
public let serverHost: String
|
||||
public let serverPort: Int
|
||||
|
||||
var internalStatus: SOCKS5AdapterStatus = .invalid
|
||||
|
||||
let helloData = Data([0x05, 0x01, 0x00])
|
||||
|
||||
public enum ReadTag: Int {
|
||||
case methodResponse = -20000, connectResponseFirstPart, connectResponseSecondPart
|
||||
}
|
||||
|
||||
public enum WriteTag: Int {
|
||||
case open = -21000, connectIPv4, connectIPv6, connectDomainLength, connectPort
|
||||
}
|
||||
|
||||
public init(serverHost: String, serverPort: Int) {
|
||||
self.serverHost = serverHost
|
||||
self.serverPort = serverPort
|
||||
super.init()
|
||||
}
|
||||
|
||||
public override func openSocketWith(session: ConnectSession) {
|
||||
super.openSocketWith(session: session)
|
||||
|
||||
guard !isCancelled else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
internalStatus = .connecting
|
||||
try socket.connectTo(host: serverHost, port: serverPort, enableTLS: false, tlsSettings: nil)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
public override func didConnectWith(socket: RawTCPSocketProtocol) {
|
||||
super.didConnectWith(socket: socket)
|
||||
|
||||
write(data: helloData)
|
||||
internalStatus = .readingMethodResponse
|
||||
socket.readDataTo(length: 2)
|
||||
}
|
||||
|
||||
public override func didRead(data: Data, from socket: RawTCPSocketProtocol) {
|
||||
super.didRead(data: data, from: socket)
|
||||
|
||||
switch internalStatus {
|
||||
case .readingMethodResponse:
|
||||
var response: [UInt8]
|
||||
if session.isIPv4() {
|
||||
response = [0x05, 0x01, 0x00, 0x01]
|
||||
let address = IPAddress(fromString: session.host)!
|
||||
response += [UInt8](address.dataInNetworkOrder)
|
||||
} else if session.isIPv6() {
|
||||
response = [0x05, 0x01, 0x00, 0x04]
|
||||
let address = IPAddress(fromString: session.host)!
|
||||
response += [UInt8](address.dataInNetworkOrder)
|
||||
} else {
|
||||
response = [0x05, 0x01, 0x00, 0x03]
|
||||
response.append(UInt8(session.host.utf8.count))
|
||||
response += [UInt8](session.host.utf8)
|
||||
}
|
||||
|
||||
let portBytes: [UInt8] = Utils.toByteArray(UInt16(session.port)).reversed()
|
||||
response.append(contentsOf: portBytes)
|
||||
write(data: Data(response))
|
||||
|
||||
internalStatus = .readingResponseFirstPart
|
||||
socket.readDataTo(length: 5)
|
||||
case .readingResponseFirstPart:
|
||||
var readLength = 0
|
||||
switch data[3] {
|
||||
case 1:
|
||||
readLength = 3 + 2
|
||||
case 3:
|
||||
readLength = Int(data[4]) + 2
|
||||
case 4:
|
||||
readLength = 15 + 2
|
||||
default:
|
||||
break
|
||||
}
|
||||
internalStatus = .readingResponseSecondPart
|
||||
socket.readDataTo(length: readLength)
|
||||
case .readingResponseSecondPart:
|
||||
internalStatus = .forwarding
|
||||
observer?.signal(.readyForForward(self))
|
||||
delegate?.didBecomeReadyToForwardWith(socket: self)
|
||||
case .forwarding:
|
||||
delegate?.didRead(data: data, from: self)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override open func didWrite(data: Data?, by socket: RawTCPSocketProtocol) {
|
||||
super.didWrite(data: data, by: socket)
|
||||
|
||||
if internalStatus == .forwarding {
|
||||
delegate?.didWrite(data: data, by: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// This adapter connects to remote host through a HTTP proxy with SSL.
|
||||
public class SecureHTTPAdapter: HTTPAdapter {
|
||||
override public init(serverHost: String, serverPort: Int, auth: HTTPAuthentication?) {
|
||||
super.init(serverHost: serverHost, serverPort: serverPort, auth: auth)
|
||||
secured = true
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// This class just forwards data directly.
|
||||
/// - note: It is designed to work with tun2socks only.
|
||||
public class DirectProxySocket: ProxySocket {
|
||||
enum DirectProxyReadStatus: CustomStringConvertible {
|
||||
case invalid,
|
||||
forwarding,
|
||||
stopped
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .invalid:
|
||||
return "invalid"
|
||||
case .forwarding:
|
||||
return "forwarding"
|
||||
case .stopped:
|
||||
return "stopped"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum DirectProxyWriteStatus {
|
||||
case invalid,
|
||||
forwarding,
|
||||
stopped
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .invalid:
|
||||
return "invalid"
|
||||
case .forwarding:
|
||||
return "forwarding"
|
||||
case .stopped:
|
||||
return "stopped"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var readStatus: DirectProxyReadStatus = .invalid
|
||||
private var writeStatus: DirectProxyWriteStatus = .invalid
|
||||
|
||||
public var readStatusDescription: String {
|
||||
return readStatus.description
|
||||
}
|
||||
|
||||
public var writeStatusDescription: String {
|
||||
return writeStatus.description
|
||||
}
|
||||
|
||||
/**
|
||||
Begin reading and processing data from the socket.
|
||||
|
||||
- note: Since there is nothing to read and process before forwarding data, this just calls `delegate?.didReceiveRequest`.
|
||||
*/
|
||||
override public func openSocket() {
|
||||
super.openSocket()
|
||||
|
||||
guard !isCancelled else {
|
||||
return
|
||||
}
|
||||
|
||||
if let address = socket.destinationIPAddress, let port = socket.destinationPort {
|
||||
session = ConnectSession(host: address.presentation, port: Int(port.value))
|
||||
|
||||
observer?.signal(.receivedRequest(session!, on: self))
|
||||
delegate?.didReceive(session: session!, from: self)
|
||||
} else {
|
||||
forceDisconnect()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Response to the `AdapterSocket` on the other side of the `Tunnel` which has succefully connected to the remote server.
|
||||
|
||||
- parameter adapter: The `AdapterSocket`.
|
||||
*/
|
||||
override public func respondTo(adapter: AdapterSocket) {
|
||||
super.respondTo(adapter: adapter)
|
||||
|
||||
guard !isCancelled else {
|
||||
return
|
||||
}
|
||||
|
||||
readStatus = .forwarding
|
||||
writeStatus = .forwarding
|
||||
|
||||
observer?.signal(.readyForForward(self))
|
||||
delegate?.didBecomeReadyToForwardWith(socket: self)
|
||||
}
|
||||
|
||||
/**
|
||||
The socket did read some data.
|
||||
|
||||
- parameter data: The data read from the socket.
|
||||
- parameter from: The socket where the data is read from.
|
||||
*/
|
||||
override open func didRead(data: Data, from: RawTCPSocketProtocol) {
|
||||
super.didRead(data: data, from: from)
|
||||
delegate?.didRead(data: data, from: self)
|
||||
}
|
||||
|
||||
/**
|
||||
The socket did send some data.
|
||||
|
||||
- parameter data: The data which have been sent to remote (acknowledged). Note this may not be available since the data may be released to save memory.
|
||||
- parameter by: The socket where the data is sent out.
|
||||
*/
|
||||
override open func didWrite(data: Data?, by: RawTCPSocketProtocol) {
|
||||
super.didWrite(data: data, by: by)
|
||||
delegate?.didWrite(data: data, by: self)
|
||||
}
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
public class SOCKS5ProxySocket: ProxySocket {
|
||||
enum SOCKS5ProxyReadStatus: CustomStringConvertible {
|
||||
case invalid,
|
||||
readingVersionIdentifierAndNumberOfMethods,
|
||||
readingMethods,
|
||||
readingConnectHeader,
|
||||
readingIPv4Address,
|
||||
readingDomainLength,
|
||||
readingDomain,
|
||||
readingIPv6Address,
|
||||
readingPort,
|
||||
forwarding,
|
||||
stopped
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .invalid:
|
||||
return "invalid"
|
||||
case .readingVersionIdentifierAndNumberOfMethods:
|
||||
return "reading version and methods"
|
||||
case .readingMethods:
|
||||
return "reading methods"
|
||||
case .readingConnectHeader:
|
||||
return "reading connect header"
|
||||
case .readingIPv4Address:
|
||||
return "IPv4 address"
|
||||
case .readingDomainLength:
|
||||
return "domain length"
|
||||
case .readingDomain:
|
||||
return "domain"
|
||||
case .readingIPv6Address:
|
||||
return "IPv6 address"
|
||||
case .readingPort:
|
||||
return "reading port"
|
||||
case .forwarding:
|
||||
return "forwarding"
|
||||
case .stopped:
|
||||
return "stopped"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum SOCKS5ProxyWriteStatus: CustomStringConvertible {
|
||||
case invalid,
|
||||
sendingResponse,
|
||||
forwarding,
|
||||
stopped
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .invalid:
|
||||
return "invalid"
|
||||
case .sendingResponse:
|
||||
return "sending response"
|
||||
case .forwarding:
|
||||
return "forwarding"
|
||||
case .stopped:
|
||||
return "stopped"
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The remote host to connect to.
|
||||
public var destinationHost: String!
|
||||
|
||||
/// The remote port to connect to.
|
||||
public var destinationPort: Int!
|
||||
|
||||
private var readStatus: SOCKS5ProxyReadStatus = .invalid
|
||||
private var writeStatus: SOCKS5ProxyWriteStatus = .invalid
|
||||
|
||||
public var readStatusDescription: String {
|
||||
return readStatus.description
|
||||
}
|
||||
|
||||
public var writeStatusDescription: String {
|
||||
return writeStatus.description
|
||||
}
|
||||
|
||||
/**
|
||||
Begin reading and processing data from the socket.
|
||||
*/
|
||||
override public func openSocket() {
|
||||
super.openSocket()
|
||||
|
||||
guard !isCancelled else {
|
||||
return
|
||||
}
|
||||
|
||||
readStatus = .readingVersionIdentifierAndNumberOfMethods
|
||||
socket.readDataTo(length: 2)
|
||||
}
|
||||
|
||||
// swiftlint:disable function_body_length
|
||||
// swiftlint:disable cyclomatic_complexity
|
||||
/**
|
||||
The socket did read some data.
|
||||
|
||||
- parameter data: The data read from the socket.
|
||||
- parameter from: The socket where the data is read from.
|
||||
*/
|
||||
override public func didRead(data: Data, from: RawTCPSocketProtocol) {
|
||||
super.didRead(data: data, from: from)
|
||||
|
||||
switch readStatus {
|
||||
case .forwarding:
|
||||
delegate?.didRead(data: data, from: self)
|
||||
case .readingVersionIdentifierAndNumberOfMethods:
|
||||
data.withUnsafeBytes { pointer in
|
||||
let p = pointer.bindMemory(to: Int8.self)
|
||||
|
||||
guard p.baseAddress!.pointee == 5 else {
|
||||
// TODO: notify observer
|
||||
self.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
guard p.baseAddress!.successor().pointee > 0 else {
|
||||
// TODO: notify observer
|
||||
self.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
self.readStatus = .readingMethods
|
||||
self.socket.readDataTo(length: Int(p.baseAddress!.successor().pointee))
|
||||
}
|
||||
case .readingMethods:
|
||||
// TODO: check for 0x00 in read data
|
||||
|
||||
let response = Data([0x05, 0x00])
|
||||
// we would not be able to read anything before the data is written out, so no need to handle the dataWrote event.
|
||||
write(data: response)
|
||||
readStatus = .readingConnectHeader
|
||||
socket.readDataTo(length: 4)
|
||||
case .readingConnectHeader:
|
||||
data.withUnsafeBytes { pointer in
|
||||
let p = pointer.bindMemory(to: Int8.self)
|
||||
|
||||
guard p.baseAddress!.pointee == 5 && p.baseAddress!.successor().pointee == 1 else {
|
||||
// TODO: notify observer
|
||||
self.disconnect()
|
||||
return
|
||||
}
|
||||
switch p.baseAddress!.advanced(by: 3).pointee {
|
||||
case 1:
|
||||
readStatus = .readingIPv4Address
|
||||
socket.readDataTo(length: 4)
|
||||
case 3:
|
||||
readStatus = .readingDomainLength
|
||||
socket.readDataTo(length: 1)
|
||||
case 4:
|
||||
readStatus = .readingIPv6Address
|
||||
socket.readDataTo(length: 16)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
case .readingIPv4Address:
|
||||
var address = Data(count: Int(INET_ADDRSTRLEN))
|
||||
_ = data.withUnsafeBytes { data_ptr in
|
||||
address.withUnsafeMutableBytes { addr_ptr in
|
||||
inet_ntop(AF_INET, data_ptr.baseAddress!, addr_ptr.bindMemory(to: Int8.self).baseAddress!, socklen_t(INET_ADDRSTRLEN))
|
||||
}
|
||||
}
|
||||
|
||||
destinationHost = String(data: address, encoding: .utf8)
|
||||
|
||||
readStatus = .readingPort
|
||||
socket.readDataTo(length: 2)
|
||||
case .readingIPv6Address:
|
||||
var address = Data(count: Int(INET6_ADDRSTRLEN))
|
||||
_ = data.withUnsafeBytes { data_ptr in
|
||||
address.withUnsafeMutableBytes { addr_ptr in
|
||||
inet_ntop(AF_INET6, data_ptr.baseAddress!, addr_ptr.bindMemory(to: Int8.self).baseAddress!, socklen_t(INET6_ADDRSTRLEN))
|
||||
}
|
||||
}
|
||||
|
||||
destinationHost = String(data: address, encoding: .utf8)
|
||||
|
||||
readStatus = .readingPort
|
||||
socket.readDataTo(length: 2)
|
||||
case .readingDomainLength:
|
||||
readStatus = .readingDomain
|
||||
socket.readDataTo(length: Int(data.first!))
|
||||
case .readingDomain:
|
||||
destinationHost = String(data: data, encoding: .utf8)
|
||||
readStatus = .readingPort
|
||||
socket.readDataTo(length: 2)
|
||||
case .readingPort:
|
||||
data.withUnsafeBytes {
|
||||
destinationPort = Int($0.load(as: UInt16.self).bigEndian)
|
||||
}
|
||||
|
||||
readStatus = .forwarding
|
||||
session = ConnectSession(host: destinationHost, port: destinationPort)
|
||||
observer?.signal(.receivedRequest(session!, on: self))
|
||||
delegate?.didReceive(session: session!, from: self)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The socket did send some data.
|
||||
|
||||
- parameter data: The data which have been sent to remote (acknowledged). Note this may not be available since the data may be released to save memory.
|
||||
- parameter from: The socket where the data is sent out.
|
||||
*/
|
||||
override public func didWrite(data: Data?, by: RawTCPSocketProtocol) {
|
||||
super.didWrite(data: data, by: by)
|
||||
|
||||
switch writeStatus {
|
||||
case .forwarding:
|
||||
delegate?.didWrite(data: data, by: self)
|
||||
case .sendingResponse:
|
||||
writeStatus = .forwarding
|
||||
observer?.signal(.readyForForward(self))
|
||||
delegate?.didBecomeReadyToForwardWith(socket: self)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Response to the `AdapterSocket` on the other side of the `Tunnel` which has succefully connected to the remote server.
|
||||
|
||||
- parameter adapter: The `AdapterSocket`.
|
||||
*/
|
||||
override public func respondTo(adapter: AdapterSocket) {
|
||||
super.respondTo(adapter: adapter)
|
||||
|
||||
guard !isCancelled else {
|
||||
return
|
||||
}
|
||||
|
||||
var responseBytes = [UInt8](repeating: 0, count: 10)
|
||||
responseBytes[0...3] = [0x05, 0x00, 0x00, 0x01]
|
||||
let responseData = Data(responseBytes)
|
||||
|
||||
writeStatus = .sendingResponse
|
||||
write(data: responseData)
|
||||
}
|
||||
}
|
||||
@@ -170,9 +170,7 @@ public class Tunnel: NSObject, SocketDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let manager = RuleManager.currentManager
|
||||
let factory = manager.match(session)!
|
||||
adapterSocket = factory.getAdapterFor(session: session)
|
||||
adapterSocket = DirectAdapterFactory().getAdapterFor(session: session)
|
||||
adapterSocket!.delegate = self
|
||||
adapterSocket!.openSocketWith(session: session)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -15,8 +15,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
db.initAppOnlyScheme()
|
||||
}
|
||||
|
||||
Prefs.registerDefaults()
|
||||
PrefsShared.registerDefaults()
|
||||
|
||||
#if IOS_SIMULATOR
|
||||
TestDataSource.load()
|
||||
SimulatorVPN.load()
|
||||
#endif
|
||||
|
||||
sync.start()
|
||||
@@ -30,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?()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,85 @@
|
||||
import Foundation
|
||||
|
||||
enum Prefs {
|
||||
private static func Int(_ key: String) -> Int { UserDefaults.standard.integer(forKey: key) }
|
||||
private static func Int(_ val: Int, _ key: String) { UserDefaults.standard.set(val, forKey: key) }
|
||||
private static func Bool(_ key: String) -> Bool { UserDefaults.standard.bool(forKey: key) }
|
||||
private static func Bool(_ val: Bool, _ key: String) { UserDefaults.standard.set(val, forKey: key) }
|
||||
private static func `Any`(_ key: String) -> Any? { UserDefaults.standard.object(forKey: key) }
|
||||
private static func `Any`(_ val: Any?, _ key: String) { UserDefaults.standard.set(val, forKey: key) }
|
||||
private static var suite: UserDefaults { UserDefaults.standard }
|
||||
|
||||
private static func Int(_ key: String) -> Int { suite.integer(forKey: key) }
|
||||
private static func Int(_ key: String, _ val: Int) { suite.set(val, forKey: key) }
|
||||
private static func Bool(_ key: String) -> Bool { suite.bool(forKey: key) }
|
||||
private static func Bool(_ key: String, _ val: Bool) { suite.set(val, forKey: key) }
|
||||
private static func Str(_ key: String) -> String? { suite.string(forKey: key) }
|
||||
private static func Str(_ key: String, _ val: String?) { suite.set(val, forKey: key) }
|
||||
private static func Obj(_ key: String) -> Any? { suite.object(forKey: key) }
|
||||
private static func Obj(_ key: String, _ val: Any?) { suite.set(val, forKey: key) }
|
||||
|
||||
static func registerDefaults() {
|
||||
suite.register(defaults: [
|
||||
"RecordingReminderEnabled" : true,
|
||||
"contextAnalyisCoOccurrenceTime" : 5,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Tutorial
|
||||
|
||||
extension Prefs {
|
||||
enum DidShowTutorial {
|
||||
static var Welcome: Bool {
|
||||
get { Prefs.Bool("didShowTutorialAppWelcome") }
|
||||
set { Prefs.Bool(newValue, "didShowTutorialAppWelcome") }
|
||||
set { Prefs.Bool("didShowTutorialAppWelcome", newValue) }
|
||||
}
|
||||
static var Recordings: Bool {
|
||||
get { Prefs.Bool("didShowTutorialRecordings") }
|
||||
set { Prefs.Bool(newValue, "didShowTutorialRecordings") }
|
||||
}
|
||||
}
|
||||
enum ContextAnalyis {
|
||||
static var CoOccurrenceTime: Int? {
|
||||
get { Prefs.Any("contextAnalyisCoOccurrenceTime") as? Int }
|
||||
set { Prefs.Any(newValue, "contextAnalyisCoOccurrenceTime") }
|
||||
set { Prefs.Bool("didShowTutorialRecordings", newValue) }
|
||||
}
|
||||
static var RecordingHowTo: Bool {
|
||||
get { Prefs.Bool("didShowTutorialRecordingHowTo") }
|
||||
set { Prefs.Bool("didShowTutorialRecordingHowTo", newValue) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Date Filter
|
||||
|
||||
enum DateFilterKind: Int {
|
||||
case Off = 0, LastXMin = 1, ABRange = 2;
|
||||
}
|
||||
enum DateFilterOrderBy: Int {
|
||||
case Date = 0, Name = 1, Count = 2;
|
||||
}
|
||||
|
||||
extension Prefs {
|
||||
enum DateFilter {
|
||||
static var Kind: DateFilterKind {
|
||||
get { DateFilterKind(rawValue: Prefs.Int("dateFilterType"))! }
|
||||
set { Prefs.Int(newValue.rawValue, "dateFilterType") }
|
||||
set { Prefs.Int("dateFilterType", newValue.rawValue) }
|
||||
}
|
||||
/// Default: `0` (disabled)
|
||||
static var LastXMin: Int {
|
||||
get { Prefs.Int("dateFilterLastXMin") }
|
||||
set { Prefs.Int(newValue, "dateFilterLastXMin") }
|
||||
set { Prefs.Int("dateFilterLastXMin", newValue) }
|
||||
}
|
||||
/// Default: `nil` (disabled)
|
||||
static var RangeA: Timestamp? {
|
||||
get { Prefs.Any("dateFilterRangeA") as? Timestamp }
|
||||
set { Prefs.Any(newValue, "dateFilterRangeA") }
|
||||
get { Prefs.Obj("dateFilterRangeA") as? Timestamp }
|
||||
set { Prefs.Obj("dateFilterRangeA", newValue) }
|
||||
}
|
||||
/// Default: `nil` (disabled)
|
||||
static var RangeB: Timestamp? {
|
||||
get { Prefs.Any("dateFilterRangeB") as? Timestamp }
|
||||
set { Prefs.Any(newValue, "dateFilterRangeB") }
|
||||
get { Prefs.Obj("dateFilterRangeB") as? Timestamp }
|
||||
set { Prefs.Obj("dateFilterRangeB", newValue) }
|
||||
}
|
||||
/// default: `.Date`
|
||||
static var OrderBy: DateFilterOrderBy {
|
||||
get { DateFilterOrderBy(rawValue: Prefs.Int("dateFilterOderType"))! }
|
||||
set { Prefs.Int(newValue.rawValue, "dateFilterOderType") }
|
||||
set { Prefs.Int("dateFilterOderType", newValue.rawValue) }
|
||||
}
|
||||
/// default: `false` (Desc)
|
||||
static var OrderAsc: Bool {
|
||||
get { Prefs.Bool("dateFilterOderAsc") }
|
||||
set { Prefs.Bool(newValue, "dateFilterOderAsc") }
|
||||
set { Prefs.Bool("dateFilterOderAsc", newValue) }
|
||||
}
|
||||
|
||||
/// - Returns: Timestamp restriction depending on current selected date filter.
|
||||
@@ -69,9 +96,31 @@ enum Prefs {
|
||||
}
|
||||
}
|
||||
}
|
||||
enum DateFilterKind: Int {
|
||||
case Off = 0, LastXMin = 1, ABRange = 2;
|
||||
|
||||
|
||||
// MARK: - ContextAnalyis
|
||||
|
||||
extension Prefs {
|
||||
enum ContextAnalyis {
|
||||
static var CoOccurrenceTime: Int {
|
||||
get { Prefs.Int("contextAnalyisCoOccurrenceTime") }
|
||||
set { Prefs.Int("contextAnalyisCoOccurrenceTime", newValue) }
|
||||
}
|
||||
}
|
||||
}
|
||||
enum DateFilterOrderBy: Int {
|
||||
case Date = 0, Name = 1, Count = 2;
|
||||
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
extension Prefs {
|
||||
enum RecordingReminder {
|
||||
static var Enabled: Bool {
|
||||
get { Prefs.Bool("RecordingReminderEnabled") }
|
||||
set { Prefs.Bool("RecordingReminderEnabled", newValue) }
|
||||
}
|
||||
static var Sound: String {
|
||||
get { Prefs.Str("RecordingReminderSound") ?? "#default" }
|
||||
set { Prefs.Str("RecordingReminderSound", newValue) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,102 @@ enum PrefsShared {
|
||||
private static var suite: UserDefaults { UserDefaults(suiteName: "group.de.uni-bamberg.psi.AppCheck")! }
|
||||
|
||||
private static func Int(_ key: String) -> Int { suite.integer(forKey: key) }
|
||||
private static func Int(_ val: Int, _ key: String) { suite.set(val, forKey: key) }
|
||||
// private static func Obj(_ key: String) -> Any? { suite.object(forKey: key) }
|
||||
// private static func Obj(_ val: Any?, _ key: String) { suite.set(val, forKey: key) }
|
||||
private static func Int(_ key: String, _ val: Int) { suite.set(val, forKey: key); suite.synchronize() }
|
||||
private static func Bool(_ key: String) -> Bool { suite.bool(forKey: key) }
|
||||
private static func Bool(_ key: String, _ val: Bool) { suite.set(val, forKey: key); suite.synchronize() }
|
||||
private static func Str(_ key: String) -> String? { suite.string(forKey: key) }
|
||||
private static func Str(_ key: String, _ val: String?) { suite.set(val, forKey: key); suite.synchronize() }
|
||||
|
||||
static func registerDefaults() {
|
||||
suite.register(defaults: [
|
||||
"ForceDisconnectSWCD" : true,
|
||||
"RestartReminderEnabled" : true,
|
||||
"RestartReminderWithText" : true,
|
||||
"RestartReminderWithBadge" : true,
|
||||
"ConnectionAlertsListsElse" : true,
|
||||
])
|
||||
}
|
||||
|
||||
static var AutoDeleteLogsDays: Int {
|
||||
get { Int("AutoDeleteLogsDays") }
|
||||
set { Int(newValue, "AutoDeleteLogsDays"); suite.synchronize() }
|
||||
set { Int("AutoDeleteLogsDays", newValue) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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 {
|
||||
enum RestartReminder {
|
||||
static var Enabled: Bool {
|
||||
get { PrefsShared.Bool("RestartReminderEnabled") }
|
||||
set { PrefsShared.Bool("RestartReminderEnabled", newValue) }
|
||||
}
|
||||
static var WithText: Bool {
|
||||
get { PrefsShared.Bool("RestartReminderWithText") }
|
||||
set { PrefsShared.Bool("RestartReminderWithText", newValue) }
|
||||
}
|
||||
static var WithBadge: Bool {
|
||||
get { PrefsShared.Bool("RestartReminderWithBadge") }
|
||||
set { PrefsShared.Bool("RestartReminderWithBadge", newValue) }
|
||||
}
|
||||
static var Sound: String {
|
||||
get { PrefsShared.Str("RestartReminderSound") ?? "#default" }
|
||||
set { PrefsShared.Str("RestartReminderSound", newValue) }
|
||||
}
|
||||
}
|
||||
enum ConnectionAlerts {
|
||||
static var Enabled: Bool {
|
||||
get { PrefsShared.Bool("ConnectionAlertsEnabled") }
|
||||
set { PrefsShared.Bool("ConnectionAlertsEnabled", newValue) }
|
||||
}
|
||||
static var Sound: String {
|
||||
get { PrefsShared.Str("ConnectionAlertsSound") ?? "#default" }
|
||||
set { PrefsShared.Str("ConnectionAlertsSound", newValue) }
|
||||
}
|
||||
static var ExcludeMode: Bool {
|
||||
get { PrefsShared.Bool("ConnectionAlertsExcludeMode") }
|
||||
set { PrefsShared.Bool("ConnectionAlertsExcludeMode", newValue) }
|
||||
}
|
||||
enum Lists {
|
||||
static var CustomA: Bool {
|
||||
get { PrefsShared.Bool("ConnectionAlertsListsCustomA") }
|
||||
set { PrefsShared.Bool("ConnectionAlertsListsCustomA", newValue) }
|
||||
}
|
||||
static var CustomB: Bool {
|
||||
get { PrefsShared.Bool("ConnectionAlertsListsCustomB") }
|
||||
set { PrefsShared.Bool("ConnectionAlertsListsCustomB", newValue) }
|
||||
}
|
||||
static var Blocked: Bool {
|
||||
get { PrefsShared.Bool("ConnectionAlertsListsBlocked") }
|
||||
set { PrefsShared.Bool("ConnectionAlertsListsBlocked", newValue) }
|
||||
}
|
||||
static var Else: Bool {
|
||||
get { PrefsShared.Bool("ConnectionAlertsListsElse") }
|
||||
set { PrefsShared.Bool("ConnectionAlertsListsElse", newValue) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
31
main/Common Classes/ThrottledBatchQueue.swift
Normal file
@@ -0,0 +1,31 @@
|
||||
import Foundation
|
||||
|
||||
class ThrottledBatchQueue<T> {
|
||||
private var cache: [T] = []
|
||||
private var scheduled: Bool = false
|
||||
private let queue: DispatchQueue
|
||||
private let delay: Double
|
||||
|
||||
init(_ delay: Double, using queue: DispatchQueue) {
|
||||
self.queue = queue
|
||||
self.delay = delay
|
||||
}
|
||||
|
||||
func addDelayed(_ elem: T, afterDelay closure: @escaping ([T]) -> Void) {
|
||||
queue.sync {
|
||||
cache.append(elem)
|
||||
guard !scheduled else {
|
||||
return
|
||||
}
|
||||
scheduled = true
|
||||
queue.asyncAfter(deadline: .now() + delay) {
|
||||
let aCopy = self.cache
|
||||
self.cache.removeAll(keepingCapacity: true)
|
||||
self.scheduled = false
|
||||
DispatchQueue.main.async {
|
||||
closure(aCopy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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,27 +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() }
|
||||
try run(sql: """
|
||||
BEGIN TRANSACTION;
|
||||
INSERT INTO heap(ts,fqdn,domain,opt) SELECT ts,domain,domainof(domain),nullif(logOpt,0) FROM req;
|
||||
DROP TABLE req;
|
||||
COMMIT;
|
||||
""")
|
||||
} catch { /* no need to migrate */ }
|
||||
}
|
||||
}
|
||||
|
||||
private enum TableName: String {
|
||||
@@ -121,11 +113,9 @@ extension SQLiteDatabase {
|
||||
guard lastRowId(.cache) > 0 else { return nil }
|
||||
let before = lastRowId(.heap) + 1
|
||||
createFunction("domainof") { ($0.first as! String).extractDomain() }
|
||||
try? run(sql:"""
|
||||
BEGIN TRANSACTION;
|
||||
transaction("""
|
||||
INSERT INTO heap(ts,fqdn,domain,opt) SELECT ts,dns,domainof(dns),nullif(opt&1,0) FROM cache;
|
||||
DELETE FROM cache;
|
||||
COMMIT;
|
||||
""")
|
||||
let after = lastRowId(.heap)
|
||||
return (before > after) ? nil : (before, after)
|
||||
@@ -299,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,
|
||||
@@ -307,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 {
|
||||
|
||||
@@ -346,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)
|
||||
}
|
||||
}
|
||||
@@ -371,31 +368,49 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get `Timestamp` of last recording.
|
||||
func recordingLastTimestamp() -> Timestamp? {
|
||||
try? run(sql: "SELECT stop FROM rec WHERE stop IS NOT NULL ORDER BY rowid DESC LIMIT 1;") {
|
||||
try ifStep($0, SQLITE_ROW)
|
||||
return col_ts($0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// `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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +79,9 @@ struct FilterOptions: OptionSet {
|
||||
static let none = FilterOptions([])
|
||||
static let blocked = FilterOptions(rawValue: 1 << 0)
|
||||
static let ignored = FilterOptions(rawValue: 1 << 1)
|
||||
static let any = FilterOptions(rawValue: 0b11)
|
||||
static let customA = FilterOptions(rawValue: 1 << 2)
|
||||
static let customB = FilterOptions(rawValue: 1 << 3)
|
||||
static let any = FilterOptions(rawValue: 0b1111)
|
||||
}
|
||||
|
||||
extension SQLiteDatabase {
|
||||
|
||||
@@ -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) }
|
||||
@@ -91,15 +92,20 @@ class SQLiteDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
/// `BEGIN TRANSACTION; \(sql); COMMIT;` on exception rollback.
|
||||
func transaction(_ sql: String) {
|
||||
do { try run(sql: "BEGIN TRANSACTION; \(sql); COMMIT;") }
|
||||
catch { rollback() }
|
||||
}
|
||||
|
||||
func ifStep(_ stmt: OpaquePointer, _ expected: Int32) throws {
|
||||
guard sqlite3_step(stmt) == expected else {
|
||||
throw SQLiteError.Step(message: errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func vacuum() {
|
||||
try? run(sql: "VACUUM;")
|
||||
}
|
||||
func vacuum() { NSLog("[SQL] VACUUM"); try? run(sql: "VACUUM;"); }
|
||||
func rollback() { NSLog("[SQL] ROLLBACK"); try? run(sql: "ROLLBACK;"); }
|
||||
}
|
||||
|
||||
|
||||
@@ -163,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,13 @@ enum DomainFilter {
|
||||
}
|
||||
|
||||
/// Get total number of blocked and ignored domains. Shown in settings overview.
|
||||
static func counts() -> (blocked: Int, ignored: Int) {
|
||||
data.reduce(into: (0, 0)) {
|
||||
static func counts() -> (blocked: Int, ignored: Int, listCustomA: Int, listCustomB: Int) {
|
||||
data.reduce(into: (0, 0, 0, 0)) {
|
||||
if $1.1.contains(.blocked) { $0.0 += 1 }
|
||||
if $1.1.contains(.ignored) { $0.1 += 1 } }
|
||||
if $1.1.contains(.ignored) { $0.1 += 1 }
|
||||
if $1.1.contains(.customA) { $0.2 += 1 }
|
||||
if $1.1.contains(.customB) { $0.3 += 1 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Union `filter` with set.
|
||||
|
||||
@@ -13,6 +13,9 @@ enum RecordingsDB {
|
||||
/// Get list of all recordings
|
||||
static func list() -> [Recording] { AppDB?.recordingGetAll() ?? [] }
|
||||
|
||||
/// Get `Timestamp` of latest recording
|
||||
static func lastTimestamp() -> Timestamp? { AppDB?.recordingLastTimestamp() }
|
||||
|
||||
/// Copy log entries from generic `heap` table to recording specific `recLog` table
|
||||
static func persist(_ r: Recording) {
|
||||
sync.syncNow { // persist changes in cache before copying recording details
|
||||
@@ -25,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)
|
||||
@@ -49,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() ?? []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ import Foundation
|
||||
|
||||
#if IOS_SIMULATOR
|
||||
|
||||
class TestDataSource {
|
||||
fileprivate var hook : GlassVPNHook!
|
||||
|
||||
class SimulatorVPN {
|
||||
static var timer: Timer?
|
||||
|
||||
static func load() {
|
||||
QLog.Debug("SQLite path: \(URL.internalDB())")
|
||||
@@ -27,13 +30,36 @@ class TestDataSource {
|
||||
db.setFilter("bi.test.com", [.blocked, .ignored])
|
||||
|
||||
QLog.Debug("Done")
|
||||
|
||||
Timer.repeating(2, call: #selector(insertRandom), on: self)
|
||||
}
|
||||
|
||||
static func start() {
|
||||
hook = GlassVPNHook()
|
||||
timer = Timer.repeating(2, call: #selector(insertRandom), on: self)
|
||||
}
|
||||
|
||||
static func stop() {
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
hook.cleanUp()
|
||||
hook = nil
|
||||
}
|
||||
|
||||
@objc static func insertRandom() {
|
||||
//QLog.Debug("Inserting 1 periodic log entry")
|
||||
try? AppDB?.logWrite("\(arc4random() % 5).count.test.com", blocked: true)
|
||||
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)") }
|
||||
}
|
||||
|
||||
static func sendMsg(_ messageData: Data) {
|
||||
hook.handleAppMessage(messageData)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -31,12 +31,21 @@ 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) {
|
||||
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
|
||||
|
||||
/// - Parameters:
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ extension Timestamp {
|
||||
static func minutes(_ m: Int) -> Timestamp { Timestamp(m * 60) }
|
||||
/// Create `Timestamp` with `h * 3600` seconds
|
||||
static func hours(_ h: Int) -> Timestamp { Timestamp(h * 3600) }
|
||||
/// Create `Timestamp` with `d * 86400` seconds
|
||||
static func days(_ d: Int) -> Timestamp { Timestamp(d * 86400) }
|
||||
}
|
||||
|
||||
extension Timer {
|
||||
@@ -84,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,3 +36,9 @@ extension UIEdgeInsets {
|
||||
self.init(top: top ?? all, left: left ?? all, bottom: bottom ?? all, right: right ?? all)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIStoryboard {
|
||||
func load<T: UIViewController>(_ identifier: String) -> T {
|
||||
instantiateViewController(withIdentifier: identifier) as! T
|
||||
}
|
||||
}
|
||||
|
||||
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.3" 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="characterWrap" numberOfLines="2" 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="characterWrap" numberOfLines="2" 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="characterWrap" numberOfLines="2" 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="characterWrap" numberOfLines="2" 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="57.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="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="characterWrap" 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.3" 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="characterWrap" numberOfLines="2" 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="characterWrap" numberOfLines="2" 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="characterWrap" numberOfLines="2" 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>
|
||||
907
main/GUI/Base.lproj/Settings.storyboard
Normal file
@@ -0,0 +1,907 @@
|
||||
<?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="qdB-ZO-LHY">
|
||||
<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>
|
||||
<!--Settings-->
|
||||
<scene sceneID="gEe-ny-NaU">
|
||||
<objects>
|
||||
<tableViewController id="qdB-ZO-LHY" customClass="TVCSettings" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="8kq-PY-wp7">
|
||||
<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"/>
|
||||
<sections>
|
||||
<tableViewSection headerTitle="VPN Proxy Settings" id="w58-6X-Jea">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="3y8-eK-09n" style="IBUITableViewCellStyleDefault" id="ghM-ze-fvp">
|
||||
<rect key="frame" x="0.0" y="55.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ghM-ze-fvp" id="d2v-vz-QIB">
|
||||
<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="VPN Proxy Enabled" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3y8-eK-09n">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZAz-WT-FDb">
|
||||
<rect key="frame" x="257" y="6" width="49" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleVPNProxy:" destination="qdB-ZO-LHY" eventType="valueChanged" id="DNS-71-2ga"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="accessoryView" destination="ZAz-WT-FDb" id="SX3-lk-I3M"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Logging Filter" id="EcH-KA-eLE">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="detailDisclosureButton" indentationWidth="10" reuseIdentifier="settingsIgnoredCell" textLabel="UdM-Zm-G9p" detailTextLabel="bHb-Tw-nPR" style="IBUITableViewCellStyleValue2" id="fZR-we-Y0k">
|
||||
<rect key="frame" x="0.0" y="155.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fZR-we-Y0k" id="eqc-fj-p0d">
|
||||
<rect key="frame" x="0.0" y="0.0" width="261" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Ignore" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="UdM-Zm-G9p">
|
||||
<rect key="frame" x="16" y="13" width="91" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="0 Domains" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bHb-Tw-nPR">
|
||||
<rect key="frame" x="113" y="13" width="73" 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>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="q3B-Yi-1bx" kind="push" identifier="segueFilterIgnored" id="EzT-Xq-wka"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="detailDisclosureButton" indentationWidth="10" reuseIdentifier="settingsBlockedCell" textLabel="fI0-Nt-Ucf" detailTextLabel="CGG-47-cdc" style="IBUITableViewCellStyleValue2" id="3pw-7c-M6R">
|
||||
<rect key="frame" x="0.0" y="199.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="3pw-7c-M6R" id="Smv-n1-917">
|
||||
<rect key="frame" x="0.0" y="0.0" width="261" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Block" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="fI0-Nt-Ucf">
|
||||
<rect key="frame" x="16" y="13" width="91" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="0 Domains" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="CGG-47-cdc">
|
||||
<rect key="frame" x="113" y="13" width="73" 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>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="q3B-Yi-1bx" kind="push" identifier="segueFilterBlocked" id="cOY-j0-75m"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Notification Settings" id="gNL-sO-BEp">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" accessoryType="disclosureIndicator" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" textLabel="pN1-lL-bGz" detailTextLabel="ldE-NT-c2c" style="IBUITableViewCellStyleValue1" id="jZA-aP-aHG">
|
||||
<rect key="frame" x="0.0" y="299.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="jZA-aP-aHG" id="OYo-TE-SLp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Reminders" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="pN1-lL-bGz">
|
||||
<rect key="frame" x="16" y="12" width="81.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="Disabled" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ldE-NT-c2c">
|
||||
<rect key="frame" x="218" y="12" width="67" 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="JYM-cs-i4H" kind="push" identifier="" id="uOT-Eo-Fm8"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" accessoryType="disclosureIndicator" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" textLabel="o7c-vQ-haI" detailTextLabel="VeV-go-DXR" style="IBUITableViewCellStyleValue1" id="OTC-Kt-LFT">
|
||||
<rect key="frame" x="0.0" y="343.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="OTC-Kt-LFT" id="RLb-Oi-WBg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Connection Alerts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="o7c-vQ-haI">
|
||||
<rect key="frame" x="16" y="12" width="137.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="Disabled" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="VeV-go-DXR">
|
||||
<rect key="frame" x="218" y="12" width="67" 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="D2a-Po-vDU" kind="push" id="6NC-bN-nVR"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Privacy Settings" id="wLR-T2-Qxm">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="8gD-At-D8n" detailTextLabel="Yy4-Ip-Wdv" style="IBUITableViewCellStyleValue1" id="Qyy-0U-yhd">
|
||||
<rect key="frame" x="0.0" y="443.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qyy-0U-yhd" id="Mfs-fu-W5k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Auto-Delete Logs" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8gD-At-D8n">
|
||||
<rect key="frame" x="16" y="12" width="134" 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="Never" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Yy4-Ip-Wdv">
|
||||
<rect key="frame" x="239.5" y="12" width="45.5" 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>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Reset Settings" id="tBs-BI-JqN">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Uii-Jp-53c">
|
||||
<rect key="frame" x="0.0" y="543.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Uii-Jp-53c" id="4Fp-Ox-yrk">
|
||||
<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="6B5-l4-Hgz">
|
||||
<rect key="frame" x="74.5" y="7" width="171" height="30"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<state key="normal" title="Reset Introduction Alerts"/>
|
||||
<connections>
|
||||
<action selector="resetTutorialAlerts:" destination="qdB-ZO-LHY" eventType="touchUpInside" id="hw8-as-4PZ"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="6B5-l4-Hgz" firstAttribute="centerY" secondItem="4Fp-Ox-yrk" secondAttribute="centerY" id="h2Y-P2-Feo"/>
|
||||
<constraint firstItem="6B5-l4-Hgz" firstAttribute="centerX" secondItem="4Fp-Ox-yrk" secondAttribute="centerX" id="jpA-gA-3jY"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Xgc-6Z-IlH">
|
||||
<rect key="frame" x="0.0" y="587.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Xgc-6Z-IlH" id="efR-vn-6MX">
|
||||
<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="sE3-Vh-0lM">
|
||||
<rect key="frame" x="111.5" y="7" width="97" height="30"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<state key="normal" title="Delete all logs">
|
||||
<color key="titleColor" systemColor="systemRedColor" red="1" green="0.23137254900000001" blue="0.18823529410000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="clearDatabaseResults" destination="qdB-ZO-LHY" eventType="touchUpInside" id="heU-m1-oJq"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="sE3-Vh-0lM" firstAttribute="centerX" secondItem="efR-vn-6MX" secondAttribute="centerX" id="TvC-jA-Wp5"/>
|
||||
<constraint firstItem="sE3-Vh-0lM" firstAttribute="centerY" secondItem="efR-vn-6MX" secondAttribute="centerY" id="WoM-cy-cAY"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Advanced Settings" id="Vlg-nm-VB3">
|
||||
<cells>
|
||||
<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="pQ5-lm-Rco" id="52Y-H3-jvJ">
|
||||
<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 unresolvable" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="rio-m6-pXN">
|
||||
<rect key="frame" x="15" 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="15" 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="15" 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="15" 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="NPx-9w-ua0"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<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>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="qdB-ZO-LHY" id="RH3-xR-dpC"/>
|
||||
<outlet property="delegate" destination="qdB-ZO-LHY" id="eYf-Xd-2Jq"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Settings" id="9Ce-p2-kGX"/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
|
||||
<connections>
|
||||
<outlet property="cellDomainsBlocked" destination="3pw-7c-M6R" id="AHT-FE-z0s"/>
|
||||
<outlet property="cellDomainsIgnored" destination="fZR-we-Y0k" id="Huy-N3-gz7"/>
|
||||
<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>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="VNK-Z0-T0a" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-200" y="0.0"/>
|
||||
</scene>
|
||||
<!--Domains-->
|
||||
<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" 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="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">
|
||||
<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="characterWrap" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.80000000000000004" id="MrS-rb-RLB">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="43.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="q3B-Yi-1bx" id="eWw-VO-n1c"/>
|
||||
<outlet property="delegate" destination="q3B-Yi-1bx" id="02X-f0-d1a"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Domains" id="FWA-IG-VIb">
|
||||
<barButtonItem key="rightBarButtonItem" systemItem="add" id="RFW-bp-wwH">
|
||||
<connections>
|
||||
<action selector="addNewFilter" destination="q3B-Yi-1bx" id="JID-eH-y0p"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Xzo-dO-WpK" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1500" y="0.0"/>
|
||||
</scene>
|
||||
<!--Reminders-->
|
||||
<scene sceneID="fWF-ss-cNz">
|
||||
<objects>
|
||||
<tableViewController id="JYM-cs-i4H" customClass="TVCReminderAlerts" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="Dop-3B-Uvh">
|
||||
<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"/>
|
||||
<sections>
|
||||
<tableViewSection headerTitle="Restart Reminder" id="UOi-fT-8Vh">
|
||||
<string key="footerTitle">If VPN stops accidentally, show a notification 5 minutes later. It will remind you to re-enable the VPN after system reboot.
|
||||
|
||||
if Notification is enabled, show a notification banner once, stating the VPN has stopped.
|
||||
|
||||
If App Badge is enabled, display the letter "1" on the homescreen app icon, as long as the VPN is not running.</string>
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="dl8-4J-a0L" style="IBUITableViewCellStyleDefault" id="Z2r-Kz-TCt">
|
||||
<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="Z2r-Kz-TCt" id="rEy-qO-PEN">
|
||||
<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="Warn If VPN Stops" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dl8-4J-a0L">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<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="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"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="accessoryView" destination="zaV-mh-eqb" id="irZ-hk-KoR"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="XC6-mj-vkg" style="IBUITableViewCellStyleDefault" id="5sU-vh-JDf">
|
||||
<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="5sU-vh-JDf" id="MDI-fb-989">
|
||||
<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="… with Notification" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="XC6-mj-vkg">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<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="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"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="accessoryView" destination="HaE-En-NH3" id="dld-32-3vc"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="wHg-Wo-szR" style="IBUITableViewCellStyleDefault" id="01e-KG-qDH">
|
||||
<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="01e-KG-qDH" id="XWV-FF-VxR">
|
||||
<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="… with App Badge" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="wHg-Wo-szR">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<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="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"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="accessoryView" destination="N2Q-cU-pkd" id="6LN-Zw-4Nf"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="UBT-zI-VNd" detailTextLabel="tj7-1l-bts" style="IBUITableViewCellStyleValue1" id="pAS-8r-oS5">
|
||||
<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="pAS-8r-oS5" id="iMr-Gb-dhX">
|
||||
<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="Sound" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="UBT-zI-VNd">
|
||||
<rect key="frame" x="16" y="12" width="49.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="tj7-1l-bts">
|
||||
<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="I37-dZ-c9Q" kind="push" identifier="segueSoundRestartReminder" id="nBh-15-nBq"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Recording Reminder" footerTitle="Very sporadic reminder. Triggered if your last recording is older than two weeks." id="9Wn-bc-wKX">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="u55-7Z-Q0Q" style="IBUITableViewCellStyleDefault" id="87B-MT-J9s">
|
||||
<rect key="frame" x="0.0" y="449" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="87B-MT-J9s" id="79D-rZ-spg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Recording Reminder" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="u55-7Z-Q0Q">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<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="48" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleAllowRecordingReminder:" destination="JYM-cs-i4H" eventType="valueChanged" id="unC-Ur-jPM"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="accessoryView" destination="mTm-Rm-1RQ" id="MRe-3h-xfa"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="VYC-xF-awf" detailTextLabel="Ywb-pT-l1W" style="IBUITableViewCellStyleValue1" id="UkL-k7-bB7">
|
||||
<rect key="frame" x="0.0" y="492.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="UkL-k7-bB7" id="fZv-NH-YA3">
|
||||
<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="Sound" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="VYC-xF-awf">
|
||||
<rect key="frame" x="16" y="12" width="49.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="Ywb-pT-l1W">
|
||||
<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="I37-dZ-c9Q" kind="push" identifier="segueSoundRecordingReminder" id="xKB-i8-J9c"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="JYM-cs-i4H" id="1ji-9q-6qB"/>
|
||||
<outlet property="delegate" destination="JYM-cs-i4H" id="YU7-R1-VjB"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Reminders" id="Z9N-kQ-xhI"/>
|
||||
<connections>
|
||||
<outlet property="recordingAllow" destination="mTm-Rm-1RQ" id="tqz-Pk-pSi"/>
|
||||
<outlet property="recordingSound" destination="UkL-k7-bB7" id="Y9s-fL-cQU"/>
|
||||
<outlet property="restartAllow" destination="zaV-mh-eqb" id="zcQ-1e-i4H"/>
|
||||
<outlet property="restartAllowBadge" destination="N2Q-cU-pkd" id="RPU-f6-Dv3"/>
|
||||
<outlet property="restartAllowNotify" destination="HaE-En-NH3" id="1BN-5h-zNR"/>
|
||||
<outlet property="restartSound" destination="pAS-8r-oS5" id="9Dy-QR-WXq"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="W7H-LK-3wW" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-200" y="768"/>
|
||||
</scene>
|
||||
<!--Sound-->
|
||||
<scene sceneID="3a5-wn-tdm">
|
||||
<objects>
|
||||
<tableViewController id="I37-dZ-c9Q" customClass="TVCChooseAlertTone" 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="w2R-BE-lM4">
|
||||
<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" accessoryType="checkmark" indentationWidth="10" reuseIdentifier="SettingsAlertToneCell" textLabel="O50-Db-5TI" style="IBUITableViewCellStyleDefault" id="38V-eP-HSv">
|
||||
<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="38V-eP-HSv" id="hoG-Cy-sHr">
|
||||
<rect key="frame" x="0.0" y="0.0" width="280" 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="O50-Db-5TI">
|
||||
<rect key="frame" x="16" y="0.0" width="256" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<sections/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="I37-dZ-c9Q" id="aji-Ci-VQs"/>
|
||||
<outlet property="delegate" destination="I37-dZ-c9Q" id="YUg-WL-kh8"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Sound" id="5hI-rp-d1Z"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="NXc-3P-uoW" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1500" y="768"/>
|
||||
</scene>
|
||||
<!--Connection Alerts-->
|
||||
<scene sceneID="JyV-QU-Dw8">
|
||||
<objects>
|
||||
<tableViewController id="D2a-Po-vDU" customClass="TVCConnectionAlerts" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="u0e-LW-1Qh">
|
||||
<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"/>
|
||||
<sections>
|
||||
<tableViewSection headerTitle="Connection Alerts" footerTitle="Get a notification whenever a specific DNS request occurs." id="4OJ-qA-l8L">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" textLabel="9g7-rO-sIS" style="IBUITableViewCellStyleDefault" id="8hz-pm-rV5">
|
||||
<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="8hz-pm-rV5" id="fz3-2p-ave">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Enable Alerts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="9g7-rO-sIS">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<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="47" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleShowNotifications:" destination="D2a-Po-vDU" eventType="valueChanged" id="Thg-6R-7wM"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="accessoryView" destination="who-8G-voz" id="6YE-dG-ThI"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="pck-pT-tnX" detailTextLabel="l8v-5i-Zue" style="IBUITableViewCellStyleValue1" id="laE-pg-nAE">
|
||||
<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="laE-pg-nAE" id="157-KR-1R5">
|
||||
<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="Sound" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="pck-pT-tnX">
|
||||
<rect key="frame" x="16" y="12" width="49.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="l8v-5i-Zue">
|
||||
<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="I37-dZ-c9Q" kind="push" id="tUF-Kv-koO"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Operation Mode" footerTitle="Select whether you'd like to manually include or manually exclude specific domains from notifications." id="9fV-UJ-d1S">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="checkmark" indentationWidth="10" textLabel="Lug-Bp-oz0" style="IBUITableViewCellStyleDefault" id="ZMb-xn-r8o">
|
||||
<rect key="frame" x="0.0" y="234" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ZMb-xn-r8o" id="eT6-OU-8eU">
|
||||
<rect key="frame" x="0.0" y="0.0" width="280" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Notify only selected lists" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Lug-Bp-oz0">
|
||||
<rect key="frame" x="16" y="0.0" width="256" height="43.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="default" accessoryType="checkmark" indentationWidth="10" textLabel="FRE-W4-dw2" style="IBUITableViewCellStyleDefault" id="47P-B8-Sul">
|
||||
<rect key="frame" x="0.0" y="277.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="47P-B8-Sul" id="RbD-bN-NCj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="280" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Notify all, except selected" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="FRE-W4-dw2">
|
||||
<rect key="frame" x="16" y="0.0" width="256" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Include" id="Lus-cA-eCF">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="checkmark" indentationWidth="10" textLabel="be7-GU-inU" style="IBUITableViewCellStyleDefault" id="2bN-EB-rDk">
|
||||
<rect key="frame" x="0.0" y="421" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="2bN-EB-rDk" id="UxC-Sm-W4n">
|
||||
<rect key="frame" x="0.0" y="0.0" width="280" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Blocked Domains" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="be7-GU-inU">
|
||||
<rect key="frame" x="16" y="0.0" width="256" height="43.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="default" accessoryType="checkmark" indentationWidth="10" textLabel="fYg-Mq-C4Q" style="IBUITableViewCellStyleDefault" id="UTd-2r-8c7">
|
||||
<rect key="frame" x="0.0" y="464.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="UTd-2r-8c7" id="Z6Y-qL-4bw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="280" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Domains of List A" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="fYg-Mq-C4Q">
|
||||
<rect key="frame" x="16" y="0.0" width="256" height="43.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="default" accessoryType="checkmark" indentationWidth="10" textLabel="f4F-j9-uiy" style="IBUITableViewCellStyleDefault" id="dk0-Vg-0Zl">
|
||||
<rect key="frame" x="0.0" y="508" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dk0-Vg-0Zl" id="69j-ye-ziM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="280" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Domains of List B" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="f4F-j9-uiy">
|
||||
<rect key="frame" x="16" y="0.0" width="256" height="43.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="default" accessoryType="checkmark" indentationWidth="10" textLabel="RiJ-Eq-LiA" style="IBUITableViewCellStyleDefault" id="VTi-I6-dXQ">
|
||||
<rect key="frame" x="0.0" y="551.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="VTi-I6-dXQ" id="PbB-ri-ibd">
|
||||
<rect key="frame" x="0.0" y="0.0" width="280" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Not in Any List" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="RiJ-Eq-LiA">
|
||||
<rect key="frame" x="16" y="0.0" width="256" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Custom Lists" footerTitle="" id="5bN-ic-93C">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="1fx-L3-esi" detailTextLabel="SAD-ad-RUa" style="IBUITableViewCellStyleValue2" id="fzJ-h6-8Ll">
|
||||
<rect key="frame" x="0.0" y="658.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fzJ-h6-8Ll" id="u6I-My-k4J">
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="List A" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="1fx-L3-esi">
|
||||
<rect key="frame" x="16" y="13" width="91" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="0 Domains" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="SAD-ad-RUa">
|
||||
<rect key="frame" x="113" y="13" width="73" 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>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="q3B-Yi-1bx" kind="push" identifier="segueFilterListCustomA" id="2ak-aX-wVl"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="bP5-xR-UWP" detailTextLabel="3b0-Aj-9So" style="IBUITableViewCellStyleValue2" id="uTh-Xw-vp2">
|
||||
<rect key="frame" x="0.0" y="702.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="uTh-Xw-vp2" id="h1L-kH-yQX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="List B" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bP5-xR-UWP">
|
||||
<rect key="frame" x="16" y="13" width="91" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="0 Domains" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3b0-Aj-9So">
|
||||
<rect key="frame" x="113" y="13" width="73" 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>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="q3B-Yi-1bx" kind="push" identifier="segueFilterListCustomB" id="6wc-d1-VYY"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="D2a-Po-vDU" id="4t8-lb-7wO"/>
|
||||
<outlet property="delegate" destination="D2a-Po-vDU" id="I1e-X5-6xm"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Connection Alerts" id="5Re-pU-mt2"/>
|
||||
<connections>
|
||||
<outlet property="cellSound" destination="laE-pg-nAE" id="Qd5-mf-wox"/>
|
||||
<outlet property="listsCustomA" destination="fzJ-h6-8Ll" id="77h-42-70y"/>
|
||||
<outlet property="listsCustomB" destination="uTh-Xw-vp2" id="6OI-pU-Vff"/>
|
||||
<outlet property="showNotifications" destination="who-8G-voz" id="cUz-Bg-ftS"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="1z3-Sx-YAL" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="650" y="384"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="tUF-Kv-koO"/>
|
||||
<segue reference="EzT-Xq-wka"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
@@ -10,6 +10,10 @@ final class GlassVPNManager {
|
||||
private(set) var state: VPNState = .off
|
||||
|
||||
fileprivate init() {
|
||||
#if IOS_SIMULATOR
|
||||
postProcessedVPNState(.on)
|
||||
SimulatorVPN.start()
|
||||
#else
|
||||
NETunnelProviderManager.loadAllFromPreferences { managers, error in
|
||||
self.managerVPN = managers?.first {
|
||||
($0.protocolConfiguration as? NETunnelProviderProtocol)?
|
||||
@@ -24,10 +28,15 @@ final class GlassVPNManager {
|
||||
}
|
||||
}
|
||||
NSNotification.Name.NEVPNStatusDidChange.observe(call: #selector(vpnStatusChanged(_:)), on: self)
|
||||
#endif
|
||||
NotifyDNSFilterChanged.observe(call: #selector(didChangeDomainFilter), on: self)
|
||||
}
|
||||
|
||||
func setEnabled(_ newState: Bool) {
|
||||
#if IOS_SIMULATOR
|
||||
postProcessedVPNState(newState ? .on : .off)
|
||||
newState ? SimulatorVPN.start() : SimulatorVPN.stop()
|
||||
#else
|
||||
guard let mgr = self.managerVPN else {
|
||||
self.createNewVPN { manager in
|
||||
self.managerVPN = manager
|
||||
@@ -41,11 +50,18 @@ final class GlassVPNManager {
|
||||
newState ? try? mgr.connection.startVPNTunnel() : mgr.connection.stopVPNTunnel()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Notify VPN extension about changes
|
||||
/// - Returns: `true` on success, `false` if VPN is off or message could not be converted to `.utf8`
|
||||
@discardableResult func send(_ message: VPNAppMessage) -> Bool {
|
||||
#if IOS_SIMULATOR
|
||||
if state == .on, let data = message.raw {
|
||||
SimulatorVPN.sendMsg(data)
|
||||
return true
|
||||
}
|
||||
#else
|
||||
if let session = self.managerVPN?.connection as? NETunnelProviderSession,
|
||||
session.status == .connected, let data = message.raw {
|
||||
do {
|
||||
@@ -53,6 +69,7 @@ final class GlassVPNManager {
|
||||
return true
|
||||
} catch {}
|
||||
}
|
||||
#endif
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -136,4 +153,20 @@ struct VPNAppMessage {
|
||||
static func autoDelete(after interval: Int) -> Self {
|
||||
.init("auto-delete:\(interval)")
|
||||
}
|
||||
/// Only used for connection alert notifications
|
||||
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)")
|
||||
}
|
||||
}
|
||||
|
||||
168
main/GlassVPNHook.swift
Normal file
@@ -0,0 +1,168 @@
|
||||
import Foundation
|
||||
|
||||
class GlassVPNHook {
|
||||
|
||||
private let queue = DispatchQueue.init(label: "PSIGlassDNSQueue", qos: .userInteractive, target: .main)
|
||||
|
||||
private var filterDomains: [String]!
|
||||
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 isBackgroundRecording: Bool = false
|
||||
public var forceDisconnectUnresolvable: Bool = false
|
||||
public var forceDisconnectSWCD: Bool = false
|
||||
|
||||
|
||||
init() { reset() }
|
||||
|
||||
/// Reload from stored settings and rebuilt binary search tree
|
||||
private func reset() {
|
||||
reloadDomainFilter()
|
||||
setAutoDelete(PrefsShared.AutoDeleteLogsDays)
|
||||
cachedNotify = CachedConnectionAlert()
|
||||
currentlyRecording = PrefsShared.CurrentlyRecording != .Off
|
||||
isBackgroundRecording = PrefsShared.CurrentlyRecording == .Background
|
||||
forceDisconnectUnresolvable = PrefsShared.ForceDisconnectUnresolvableDNS
|
||||
forceDisconnectSWCD = PrefsShared.ForceDisconnectSWCD
|
||||
}
|
||||
|
||||
/// Invalidate auto-delete timer and release stored properties. You should nullify this instance afterwards.
|
||||
func cleanUp() {
|
||||
filterDomains = nil
|
||||
filterOptions = nil
|
||||
autoDeleteTimer?.fire() // one last time before we quit
|
||||
autoDeleteTimer?.invalidate()
|
||||
cachedNotify = nil
|
||||
currentlyRecording = false
|
||||
isBackgroundRecording = false
|
||||
forceDisconnectUnresolvable = false
|
||||
forceDisconnectSWCD = false
|
||||
}
|
||||
|
||||
/// Call this method from `PacketTunnelProvider.handleAppMessage(_:completionHandler:)`
|
||||
func handleAppMessage(_ messageData: Data) {
|
||||
let message = String(data: messageData, encoding: .utf8)
|
||||
if let msg = message, let i = msg.firstIndex(of: ":") {
|
||||
let action = msg.prefix(upTo: i)
|
||||
let value = msg.suffix(from: msg.index(after: i))
|
||||
switch action {
|
||||
case "filter-update":
|
||||
reloadDomainFilter() // TODO: reload only selected domain?
|
||||
return
|
||||
case "auto-delete":
|
||||
setAutoDelete(Int(value) ?? PrefsShared.AutoDeleteLogsDays)
|
||||
return
|
||||
case "notify-prefs-change":
|
||||
cachedNotify = CachedConnectionAlert()
|
||||
return
|
||||
case "recording-now":
|
||||
let newState = CurrentRecordingState(rawValue: Int(value) ?? 0)
|
||||
currentlyRecording = newState != .Off
|
||||
isBackgroundRecording = newState == .Background
|
||||
return
|
||||
case "disconnect-unresolvable":
|
||||
forceDisconnectUnresolvable = value == "1"
|
||||
return
|
||||
case "disconnect-swcd":
|
||||
forceDisconnectSWCD = value == "1"
|
||||
return
|
||||
default: break
|
||||
}
|
||||
}
|
||||
NSLog("[VPN.WARN] This should never happen! Received unknown handleAppMessage: \(message ?? messageData.base64EncodedString())")
|
||||
reset() // just in case we fallback to do everything
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Process DNS Request
|
||||
|
||||
/// Log domain request and post notification (if enabled).
|
||||
/// - Returns: `true` if the request shoud be blocked.
|
||||
func processDNSRequest(_ domain: String) -> Bool {
|
||||
let i = filterIndex(for: domain)
|
||||
let (block, ignore, cA, cB) = (i<0) ? (false, false, false, false) : filterOptions[i]
|
||||
if ignore, !currentlyRecording {
|
||||
return block
|
||||
}
|
||||
let blockActive = block && !currentlyRecording
|
||||
queue.async {
|
||||
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 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
|
||||
private func reloadDomainFilter() {
|
||||
let tmp = AppDB?.loadFilters()?.map({
|
||||
(String($0.reversed()), $1)
|
||||
}).sorted(by: { $0.0 < $1.0 }) ?? []
|
||||
let t1 = tmp.map { $0.0 }
|
||||
let t2 = tmp.map { ($1.contains(.blocked),
|
||||
$1.contains(.ignored),
|
||||
$1.contains(.customA),
|
||||
$1.contains(.customB)) }
|
||||
filterDomains = t1
|
||||
filterOptions = t2
|
||||
}
|
||||
|
||||
/// Lookup for reverse DNS binary tree
|
||||
private func filterIndex(for domain: String) -> Int {
|
||||
let reverseDomain = String(domain.reversed())
|
||||
var lo = 0, hi = filterDomains.count - 1
|
||||
while lo <= hi {
|
||||
let mid = (lo + hi)/2
|
||||
if filterDomains[mid] < reverseDomain {
|
||||
lo = mid + 1
|
||||
} else if reverseDomain < filterDomains[mid] {
|
||||
hi = mid - 1
|
||||
} else {
|
||||
return mid
|
||||
}
|
||||
}
|
||||
if lo > 0, reverseDomain.hasPrefix(filterDomains[lo - 1] + ".") {
|
||||
return lo - 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Auto-delete Timer
|
||||
|
||||
/// Prepare auto-delete timer with interval between 1 hr - 1 day.
|
||||
/// - Parameter days: Max age to keep when deleting
|
||||
private func setAutoDelete(_ days: Int) {
|
||||
autoDeleteTimer?.invalidate()
|
||||
guard days > 0 else { return }
|
||||
// Repeat interval uses days as hours. min 1 hr, max 24 hrs.
|
||||
let interval = TimeInterval(min(24, days) * 60 * 60)
|
||||
autoDeleteTimer = Timer.scheduledTimer(timeInterval: interval,
|
||||
target: self, selector: #selector(autoDeleteNow),
|
||||
userInfo: days, repeats: true)
|
||||
autoDeleteTimer!.fire()
|
||||
}
|
||||
|
||||
/// Callback fired when old data should be deleted.
|
||||
@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)")
|
||||
sender.fireDate = Date().addingTimeInterval(300) // retry in 5 min
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
main/Push Notifications/CachedConnectionAlert.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
struct CachedConnectionAlert {
|
||||
let enabled: Bool
|
||||
let invertedMode: Bool
|
||||
let listBlocked, listCustomA, listCustomB, listElse: Bool
|
||||
let tone: AnyObject?
|
||||
|
||||
init() {
|
||||
enabled = PrefsShared.ConnectionAlerts.Enabled
|
||||
guard #available(iOS 10.0, *), enabled else {
|
||||
invertedMode = false
|
||||
listBlocked = false
|
||||
listCustomA = false
|
||||
listCustomB = false
|
||||
listElse = false
|
||||
tone = nil
|
||||
return
|
||||
}
|
||||
invertedMode = PrefsShared.ConnectionAlerts.ExcludeMode
|
||||
listBlocked = PrefsShared.ConnectionAlerts.Lists.Blocked
|
||||
listCustomA = PrefsShared.ConnectionAlerts.Lists.CustomA
|
||||
listCustomB = PrefsShared.ConnectionAlerts.Lists.CustomB
|
||||
listElse = PrefsShared.ConnectionAlerts.Lists.Else
|
||||
tone = UNNotificationSound.from(string: PrefsShared.ConnectionAlerts.Sound)
|
||||
}
|
||||
|
||||
/// If notifications are enabled and allowed, schedule new notification. Otherwise NOOP.
|
||||
/// - Parameters:
|
||||
/// - domain: Domain will be used as unique identifier for noticiation center and in notification message.
|
||||
/// - blck: Indicator whether `domain` is part of `blocked` list
|
||||
/// - custA: Indicator whether `domain` is part of custom list `A`
|
||||
/// - custB: Indicator whether `domain` is part of custom list `B`
|
||||
func postOrIgnore(_ domain: String, blck: Bool, custA: Bool, custB: Bool) {
|
||||
if #available(iOS 10.0, *), enabled {
|
||||
let onAnyList = listBlocked && blck || listCustomA && custA || listCustomB && custB || listElse
|
||||
if invertedMode ? !onAnyList : onAnyList {
|
||||
PushNotification.scheduleConnectionAlert(domain, sound: tone as! UNNotificationSound?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
153
main/Push Notifications/PushNotification.swift
Normal file
@@ -0,0 +1,153 @@
|
||||
import UserNotifications
|
||||
|
||||
struct PushNotification {
|
||||
|
||||
enum Identifier: String {
|
||||
case YouShallRecordMoreReminder
|
||||
case CantStopMeNowReminder
|
||||
case RestInPeaceTombstone
|
||||
case AllConnectionAlertNotifications
|
||||
}
|
||||
|
||||
static func allowed(_ closure: @escaping (NotificationRequestState) -> Void) {
|
||||
guard #available(iOS 10, *) else { return }
|
||||
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
let state = NotificationRequestState(settings.authorizationStatus)
|
||||
DispatchQueue.main.async {
|
||||
closure(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Available in iOS 12+
|
||||
static func requestProvisionalOrDoNothing(_ closure: @escaping (Bool) -> Void) {
|
||||
guard #available(iOS 12, *) else { return closure(false) }
|
||||
|
||||
let opt: UNAuthorizationOptions = [.alert, .sound, .badge, .provisional, .providesAppNotificationSettings]
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: opt) { granted, _ in
|
||||
DispatchQueue.main.async {
|
||||
closure(granted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func requestAuthorization(_ closure: @escaping (Bool) -> Void) {
|
||||
guard #available(iOS 10, *) else { return }
|
||||
|
||||
var opt: UNAuthorizationOptions = [.alert, .sound, .badge]
|
||||
if #available(iOS 12, *) {
|
||||
opt.formUnion(.providesAppNotificationSettings)
|
||||
}
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: opt) { granted, _ in
|
||||
DispatchQueue.main.async {
|
||||
closure(granted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func hasPending(_ ident: Identifier, _ closure: @escaping (Bool) -> Void) {
|
||||
guard #available(iOS 10, *) else { return }
|
||||
|
||||
UNUserNotificationCenter.current().getPendingNotificationRequests {
|
||||
let hasIt = $0.contains { $0.identifier == ident.rawValue }
|
||||
DispatchQueue.main.async {
|
||||
closure(hasIt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func cancel(_ ident: Identifier, keepDelivered: Bool = false) {
|
||||
guard #available(iOS 10, *) else { return }
|
||||
|
||||
let center = UNUserNotificationCenter.current()
|
||||
guard ident != .AllConnectionAlertNotifications else {
|
||||
// remove all connection alert notifications while
|
||||
// keeping general purpose reminder notifications
|
||||
center.getDeliveredNotifications {
|
||||
var list = $0.map { $0.request.identifier }
|
||||
list.removeAll { !$0.contains(".") } // each domain (or IP) has a dot
|
||||
center.removeDeliveredNotifications(withIdentifiers: list)
|
||||
// no need to do the same for pending since con-alerts are always immediate
|
||||
}
|
||||
return
|
||||
}
|
||||
center.removePendingNotificationRequests(withIdentifiers: [ident.rawValue])
|
||||
if !keepDelivered {
|
||||
center.removeDeliveredNotifications(withIdentifiers: [ident.rawValue])
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
static func schedule(_ ident: Identifier, content: UNNotificationContent, trigger: UNNotificationTrigger? = nil, waitUntilDone: Bool = false) {
|
||||
schedule(ident.rawValue, content: content, trigger: trigger, waitUntilDone: waitUntilDone)
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
static func schedule(_ ident: String, content: UNNotificationContent, trigger: UNNotificationTrigger? = nil, waitUntilDone: Bool = false) {
|
||||
let req = UNNotificationRequest(identifier: ident, content: content, trigger: trigger)
|
||||
waitUntilDone ? req.pushAndWait() : req.push()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Reminder Alerts
|
||||
|
||||
extension PushNotification {
|
||||
/// Auto-check preferences whether `withText` is set, then schedule notification to 5 min in the future.
|
||||
static func scheduleRestartReminderBanner() {
|
||||
guard #available(iOS 10, *), PrefsShared.RestartReminder.WithText else { return }
|
||||
|
||||
schedule(.CantStopMeNowReminder,
|
||||
content: .make("AppCheck disabled",
|
||||
body: "AppCheck can't monitor network traffic because VPN has stopped.",
|
||||
sound: .from(string: PrefsShared.RestartReminder.Sound)),
|
||||
trigger: .make(Date(timeIntervalSinceNow: 5 * 60)),
|
||||
waitUntilDone: true)
|
||||
}
|
||||
|
||||
/// Auto-check preferences whether `withBadge` is set, then post badge immediatelly.
|
||||
/// - Parameter on: If `true`, set `1` on app icon. If `false`, remove badge on app icon.
|
||||
static func scheduleRestartReminderBadge(on: Bool) {
|
||||
guard #available(iOS 10, *), PrefsShared.RestartReminder.WithBadge else { return }
|
||||
|
||||
schedule(.RestInPeaceTombstone, content: .makeBadge(on ? 1 : 0), waitUntilDone: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Connection Alerts
|
||||
|
||||
extension PushNotification {
|
||||
static private let queue = ThrottledBatchQueue<String>(0.5, using: .init(label: "PSINotificationQueue", qos: .default, target: .global()))
|
||||
|
||||
/// Post new notification with given domain name. If notification already exists, increase occurrence count.
|
||||
/// - Parameter domain: Used in the description and as notification identifier.
|
||||
@available(iOS 10.0, *)
|
||||
static func scheduleConnectionAlert(_ domain: String, sound: UNNotificationSound?) {
|
||||
queue.addDelayed(domain) { batch in
|
||||
let groupSum = batch.reduce(into: [:]) { $0[$1] = ($0[$1] ?? 0) + 1 }
|
||||
scheduleConnectionAlertMulti(groupSum, sound: sound)
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal method to post a batch of counted domains.
|
||||
@available(iOS 10.0, *)
|
||||
static private func scheduleConnectionAlertMulti(_ group: [String: Int], sound: UNNotificationSound?) {
|
||||
UNUserNotificationCenter.current().getDeliveredNotifications { delivered in
|
||||
for (dom, count) in group {
|
||||
let num: Int
|
||||
if let prev = delivered.first(where: { $0.request.identifier == dom })?.request.content {
|
||||
if let p = prev.body.split(separator: "×").first, let i = Int(p) {
|
||||
num = count + i
|
||||
} else {
|
||||
num = count + 1
|
||||
}
|
||||
} else {
|
||||
num = count
|
||||
}
|
||||
schedule(dom, content: .make("DNS connection", body: num > 1 ? "\(num)× \(dom)" : dom, sound: sound))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
main/Push Notifications/PushNotificationAppOnly.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
import UserNotifications
|
||||
|
||||
extension PushNotification {
|
||||
static func scheduleRecordingReminder(force: Bool) {
|
||||
if force {
|
||||
scheduleRecordingReminder()
|
||||
} else {
|
||||
hasPending(.YouShallRecordMoreReminder) {
|
||||
if !$0 { scheduleRecordingReminder() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func scheduleRecordingReminder() {
|
||||
guard #available(iOS 10, *) else { return }
|
||||
|
||||
let now = Timestamp.now()
|
||||
var next = RecordingsDB.lastTimestamp() ?? (now - 1)
|
||||
while next < now {
|
||||
next += .days(14)
|
||||
}
|
||||
schedule(.YouShallRecordMoreReminder,
|
||||
content: .make("Start new recording",
|
||||
body: "It's been a while since your last recording …",
|
||||
sound: .from(string: Prefs.RecordingReminder.Sound)),
|
||||
trigger: .make(Date(next)))
|
||||
}
|
||||
}
|
||||
84
main/Push Notifications/UNNotification.swift
Normal file
@@ -0,0 +1,84 @@
|
||||
import UserNotifications
|
||||
|
||||
enum NotificationRequestState {
|
||||
case NotDetermined, Denied, Authorized, Provisional
|
||||
@available(iOS 10.0, *)
|
||||
init(_ from: UNAuthorizationStatus) {
|
||||
switch from {
|
||||
case .denied: self = .Denied
|
||||
case .authorized: self = .Authorized
|
||||
case .provisional: self = .Provisional
|
||||
case .notDetermined: fallthrough
|
||||
case .ephemeral: fallthrough
|
||||
@unknown default: self = .NotDetermined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
extension UNNotificationRequest {
|
||||
func push() {
|
||||
UNUserNotificationCenter.current().add(self) { error in
|
||||
if let e = error {
|
||||
NSLog("[ERROR] Can't add push notification: \(e)")
|
||||
}
|
||||
}
|
||||
}
|
||||
func pushAndWait() {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
UNUserNotificationCenter.current().add(self) { error in
|
||||
if let e = error {
|
||||
NSLog("[ERROR] Can't add push notification: \(e)")
|
||||
}
|
||||
semaphore.signal()
|
||||
}
|
||||
_ = semaphore.wait(wallTimeout: .distantFuture)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
extension UNNotificationContent {
|
||||
/// - Parameter sound: Use `#default` or `nil` to play the default tone. Use `#mute` to play no tone at all. Else use an `UNNotificationSoundName`.
|
||||
static func make(_ title: String, body: String, sound: UNNotificationSound? = .default) -> UNNotificationContent {
|
||||
let x = UNMutableNotificationContent()
|
||||
// use NSString.localizedUserNotificationString(forKey:arguments:)
|
||||
x.title = title
|
||||
x.body = body
|
||||
x.sound = sound
|
||||
return x
|
||||
}
|
||||
/// - Parameter value: `0` will remove the badge
|
||||
static func makeBadge(_ value: Int) -> UNNotificationContent {
|
||||
let x = UNMutableNotificationContent()
|
||||
x.badge = value as NSNumber?
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
extension UNNotificationTrigger {
|
||||
/// Calls `(dateMatching: components, repeats: repeats)`
|
||||
static func make(_ components: DateComponents, repeats: Bool) -> UNCalendarNotificationTrigger {
|
||||
UNCalendarNotificationTrigger(dateMatching: components, repeats: repeats)
|
||||
}
|
||||
/// Calls `(dateMatching: components(second-year), repeats: false)`
|
||||
static func make(_ date: Date) -> UNCalendarNotificationTrigger {
|
||||
let components = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute,.second], from: date)
|
||||
return UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
|
||||
}
|
||||
/// Calls `(timeInterval: time, repeats: repeats)`
|
||||
static func make(_ time: TimeInterval, repeats: Bool) -> UNTimeIntervalNotificationTrigger {
|
||||
UNTimeIntervalNotificationTrigger(timeInterval: time, repeats: repeats)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
extension UNNotificationSound {
|
||||
static func from(string: String) -> UNNotificationSound? {
|
||||
switch string {
|
||||
case "#mute": return nil
|
||||
case "#default": return .default
|
||||
case let name: return .init(named: UNNotificationSoundName(name + ".caf"))
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
279
main/Recordings/TVCShareRecording.swift
Normal file
@@ -0,0 +1,279 @@
|
||||
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)] = [
|
||||
("Notes", " – "), // see delegate below, update reloadNotes() and cellForRowAt
|
||||
("Date", self.weekInYear),
|
||||
("Rec-Length", "\(self.record.duration) sec"),
|
||||
("App-Bundle", self.record.appId ?? " – "),
|
||||
("App-Name", self.record.title ?? " – "),
|
||||
("iOS", UIDevice.current.systemVersion),
|
||||
]
|
||||
|
||||
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: 0, 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 == 0 && 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,
|
||||
"ios" : UIDevice.current.systemVersion,
|
||||
"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,57 +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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -64,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
|
||||
|
||||
@@ -118,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(didClose: {
|
||||
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(didClose: {
|
||||
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 = storyboard?.instantiateViewController(withIdentifier: "IBCoOccurrence") as? VCCoOccurrence 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,9 +14,17 @@ 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 ?? 5 // calls `didSet` and `logTimeDelta`
|
||||
selectedTime = Prefs.ContextAnalyis.CoOccurrenceTime // calls `didSet` and `logTimeDelta`
|
||||
timeSegment.removeAllSegments() // clear IB values
|
||||
for (i, time) in availableTimes.enumerated() {
|
||||
timeSegment.insertSegment(withTitle: TimeFormat(.abbreviated).from(seconds: time), at: i, animated: false)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,20 @@ class TVCDomains: UITableViewController, UISearchBarDelegate, GroupedDomainDataS
|
||||
}
|
||||
}
|
||||
|
||||
func pushOpen(domain: String) {
|
||||
let A: TVCHosts = storyboard!.load("requestsHosts")
|
||||
let B: TVCHostDetails = storyboard!.load("requestsOccurrences")
|
||||
A.parentDomain = domain.extractDomain()
|
||||
B.fullDomain = domain
|
||||
navigationController?.pushViewController(A, animated: false)
|
||||
navigationController?.pushViewController(B, animated: false)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Filter
|
||||
|
||||
@IBAction private func filterButtonTapped(_ sender: UIBarButtonItem) {
|
||||
let vc = self.storyboard!.instantiateViewController(withIdentifier: "domainFilter")
|
||||
let vc = storyboard!.load("domainFilter")
|
||||
vc.modalPresentationStyle = .custom
|
||||
if #available(iOS 13.0, *) {
|
||||
vc.isModalInPresentation = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
97
main/Settings/TVCChooseAlertTone.swift
Normal file
@@ -0,0 +1,97 @@
|
||||
import UIKit
|
||||
import AudioToolbox
|
||||
|
||||
protocol NotificationSoundChangedDelegate {
|
||||
/// Use `#mute` to disable sounds and `#default` to use default notification sound.
|
||||
func notificationSoundCurrent() -> String
|
||||
/// Called every time the user changes selection
|
||||
func notificationSoundChanged(filename: String, title: String)
|
||||
}
|
||||
|
||||
class TVCChooseAlertTone: UITableViewController {
|
||||
|
||||
var delegate: NotificationSoundChangedDelegate!
|
||||
private lazy var selected: String = delegate.notificationSoundCurrent()
|
||||
|
||||
private func playTone(_ name: String) {
|
||||
switch name {
|
||||
case "#mute": return // No Sound
|
||||
case "#default": AudioServicesPlayAlertSound(1315) // Default sound
|
||||
default:
|
||||
guard let url = Bundle.main.url(forResource: name, withExtension: "caf") else {
|
||||
preconditionFailure("Something went wrong. Sound file \(name).caf does not exist.")
|
||||
}
|
||||
var soundId: SystemSoundID = 0
|
||||
AudioServicesCreateSystemSoundID(url as CFURL, &soundId)
|
||||
AudioServicesAddSystemSoundCompletion(soundId, nil, nil, { id, _ -> Void in
|
||||
AudioServicesDisposeSystemSoundID(id)
|
||||
}, nil)
|
||||
AudioServicesPlayAlertSound(soundId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Table View Delegate
|
||||
|
||||
override func numberOfSections(in _: UITableView) -> Int {
|
||||
AvailableSounds.count
|
||||
}
|
||||
|
||||
override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
AvailableSounds[section].count
|
||||
}
|
||||
|
||||
override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
section == 1 ? "AppCheck" : nil
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "SettingsAlertToneCell")!
|
||||
let src = AvailableSounds[indexPath.section][indexPath.row]
|
||||
cell.textLabel?.text = src.title
|
||||
cell.accessoryType = (src.file == selected) ? .checkmark : .none
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
let src = AvailableSounds[indexPath.section][indexPath.row]
|
||||
selected = src.file
|
||||
tableView.reloadData() // re-apply checkmarks
|
||||
playTone(selected)
|
||||
delegate.notificationSoundChanged(filename: src.file, title: src.title)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sounds Data Source
|
||||
// afconvert input.aiff output.caf -d ima4 -f caff -v
|
||||
|
||||
fileprivate let AvailableSounds: [[(title: String, file: String)]] = [
|
||||
[ // System sounds
|
||||
("None", "#mute"),
|
||||
("Default", "#default")
|
||||
], [ // AppCheck sounds
|
||||
("Clock", "clock"),
|
||||
("Drum 1", "drum1"),
|
||||
("Drum 2", "drum2"),
|
||||
("Plop 1", "plop1"),
|
||||
("Plop 2", "plop2"),
|
||||
("Snap 1", "snap1"),
|
||||
("Snap 2", "snap2"),
|
||||
("Typewriter 1", "typewriter1"),
|
||||
("Typewriter 2", "typewriter2"),
|
||||
("Wood 1", "wood1"),
|
||||
("Wood 2", "wood2")
|
||||
]
|
||||
]
|
||||
|
||||
func AlertSoundTitle(for filename: String) -> String {
|
||||
for section in AvailableSounds {
|
||||
for row in section {
|
||||
if row.file == filename {
|
||||
return row.title
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
135
main/Settings/TVCConnectionAlerts.swift
Normal file
@@ -0,0 +1,135 @@
|
||||
import UIKit
|
||||
|
||||
class TVCConnectionAlerts: UITableViewController {
|
||||
|
||||
@IBOutlet var showNotifications: UISwitch!
|
||||
@IBOutlet var cellSound: UITableViewCell!
|
||||
|
||||
@IBOutlet var listsCustomA: UITableViewCell!
|
||||
@IBOutlet var listsCustomB: UITableViewCell!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
cascadeEnableConnAlert(PrefsShared.ConnectionAlerts.Enabled)
|
||||
cellSound.detailTextLabel?.text = AlertSoundTitle(for: PrefsShared.ConnectionAlerts.Sound)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
let (_, _, custA, custB) = DomainFilter.counts()
|
||||
listsCustomA.detailTextLabel?.text = "\(custA) Domains"
|
||||
listsCustomB.detailTextLabel?.text = "\(custB) Domains"
|
||||
}
|
||||
|
||||
private func cascadeEnableConnAlert(_ flag: Bool) {
|
||||
showNotifications.isOn = flag
|
||||
// en/disable related controls
|
||||
}
|
||||
|
||||
private func getListSelected(_ index: Int) -> Bool {
|
||||
switch index {
|
||||
case 0: return PrefsShared.ConnectionAlerts.Lists.Blocked
|
||||
case 1: return PrefsShared.ConnectionAlerts.Lists.CustomA
|
||||
case 2: return PrefsShared.ConnectionAlerts.Lists.CustomB
|
||||
case 3: return PrefsShared.ConnectionAlerts.Lists.Else
|
||||
default: preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private func setListSelected(_ index: Int, _ value: Bool) {
|
||||
switch index {
|
||||
case 0: PrefsShared.ConnectionAlerts.Lists.Blocked = value
|
||||
case 1: PrefsShared.ConnectionAlerts.Lists.CustomA = value
|
||||
case 2: PrefsShared.ConnectionAlerts.Lists.CustomB = value
|
||||
case 3: PrefsShared.ConnectionAlerts.Lists.Else = value
|
||||
default: preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Toggles
|
||||
|
||||
@IBAction private func toggleShowNotifications(_ sender: UISwitch) {
|
||||
PrefsShared.ConnectionAlerts.Enabled = sender.isOn
|
||||
cascadeEnableConnAlert(sender.isOn)
|
||||
GlassVPN.send(.notificationSettingsChanged())
|
||||
if sender.isOn {
|
||||
PushNotification.requestAuthorization { granted in
|
||||
if !granted {
|
||||
NotificationsDisabledAlert(presentIn: self)
|
||||
self.cascadeEnableConnAlert(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PushNotification.cancel(.AllConnectionAlertNotifications)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Table View Delegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||
let checked: Bool
|
||||
switch indexPath.section {
|
||||
case 1: // mode selection
|
||||
checked = (indexPath.row == (PrefsShared.ConnectionAlerts.ExcludeMode ? 1 : 0))
|
||||
case 2: // include & exclude lists
|
||||
checked = getListSelected(indexPath.row)
|
||||
default: return cell // process only checkmarked cells
|
||||
}
|
||||
cell.accessoryType = checked ? .checkmark : .none
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
switch indexPath.section {
|
||||
case 1: // mode selection
|
||||
PrefsShared.ConnectionAlerts.ExcludeMode = indexPath.row == 1
|
||||
tableView.reloadSections(.init(integer: 2), with: .none)
|
||||
case 2: // include & exclude lists
|
||||
let prev = tableView.cellForRow(at: indexPath)?.accessoryType == .checkmark
|
||||
setListSelected(indexPath.row, !prev)
|
||||
default: return // process only checkmarked cells
|
||||
}
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
tableView.reloadSections(.init(integer: indexPath.section), with: .none)
|
||||
GlassVPN.send(.notificationSettingsChanged())
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
if section == 2 {
|
||||
return PrefsShared.ConnectionAlerts.ExcludeMode ? "Exclude All" : "Include All"
|
||||
}
|
||||
return super.tableView(tableView, titleForHeaderInSection: section)
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if let dest = segue.destination as? TVCFilter {
|
||||
switch segue.identifier {
|
||||
case "segueFilterListCustomA":
|
||||
dest.navigationItem.title = "Custom List A"
|
||||
dest.currentFilter = .customA
|
||||
case "segueFilterListCustomB":
|
||||
dest.navigationItem.title = "Custom List B"
|
||||
dest.currentFilter = .customB
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if let tvc = segue.destination as? TVCChooseAlertTone {
|
||||
tvc.delegate = self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sound Selection
|
||||
|
||||
extension TVCConnectionAlerts: NotificationSoundChangedDelegate {
|
||||
func notificationSoundCurrent() -> String {
|
||||
PrefsShared.ConnectionAlerts.Sound
|
||||
}
|
||||
|
||||
func notificationSoundChanged(filename: String, title: String) {
|
||||
cellSound.detailTextLabel?.text = title
|
||||
PrefsShared.ConnectionAlerts.Sound = filename
|
||||
GlassVPN.send(.notificationSettingsChanged())
|
||||
}
|
||||
}
|
||||