Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
412d533275 | ||
|
|
245bb46e4f | ||
|
|
70508c1325 | ||
|
|
b44fd788b5 | ||
|
|
80f3503e16 | ||
|
|
d0056c0275 | ||
|
|
e7560479ee | ||
|
|
ed5298f7a2 | ||
|
|
647eca310f | ||
|
|
515c296b26 | ||
|
|
61ae50cdfa | ||
|
|
fcb6e9c5dd | ||
|
|
79f836016a | ||
|
|
144773ddaa |
@@ -8,6 +8,9 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
540C6457240D929300E948F9 /* EditableRows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540C6456240D929300E948F9 /* EditableRows.swift */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
@@ -18,9 +21,13 @@
|
||||
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, ); }; };
|
||||
544C95262407B1C700AB89D0 /* SharedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544C95252407B1C700AB89D0 /* SharedState.swift */; };
|
||||
5458EBC0243A3F2200CFEB15 /* TVCRecordingDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */; };
|
||||
545DDDCF243E6267003B6544 /* TutorialSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDCE243E6267003B6544 /* TutorialSheet.swift */; };
|
||||
545DDDD124436983003B6544 /* QuickUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDD024436983003B6544 /* QuickUI.swift */; };
|
||||
545DDDD424466D37003B6544 /* AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DDDD324466D37003B6544 /* AutoLayout.swift */; };
|
||||
546063E523FEFAFE008F505A /* SQDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7562223D7B2DC008F0C41 /* SQDB.swift */; };
|
||||
54751E512423955100168273 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54751E502423955000168273 /* FileManager.swift */; };
|
||||
54751E522423955100168273 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54751E502423955000168273 /* FileManager.swift */; };
|
||||
54751E512423955100168273 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54751E502423955000168273 /* URL.swift */; };
|
||||
54751E522423955100168273 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54751E502423955000168273 /* URL.swift */; };
|
||||
54953E3323DC752E0054345C /* SQDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B7562223D7B2DC008F0C41 /* SQDB.swift */; };
|
||||
54953E5F23DEBE840054345C /* TVCDomains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54953E5E23DEBE840054345C /* TVCDomains.swift */; };
|
||||
54953E6123E0D69A0054345C /* TVCHosts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54953E6023E0D69A0054345C /* TVCHosts.swift */; };
|
||||
@@ -32,7 +39,7 @@
|
||||
54B345A6241BB982004C53CC /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345A5241BB982004C53CC /* Notifications.swift */; };
|
||||
54B345A9241BBA0B004C53CC /* Generic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345A8241BBA0B004C53CC /* Generic.swift */; };
|
||||
54B345AB241BBA5B004C53CC /* AlertSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345AA241BBA5B004C53CC /* AlertSheet.swift */; };
|
||||
54B345AD241BBB00004C53CC /* GroupedDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B345AC241BBB00004C53CC /* GroupedDomain.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 */; };
|
||||
54C056DD23E9EEF700214A3F /* BundleIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C056DC23E9EEF700214A3F /* BundleIcon.swift */; };
|
||||
@@ -86,8 +93,6 @@
|
||||
54CA02952426B2FD003A5E04 /* DNSEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02242426B2FC003A5E04 /* DNSEnums.swift */; };
|
||||
54CA02962426B2FD003A5E04 /* PacketProtocolParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02262426B2FC003A5E04 /* PacketProtocolParser.swift */; };
|
||||
54CA02972426B2FD003A5E04 /* IPPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02272426B2FC003A5E04 /* IPPacket.swift */; };
|
||||
54CA02982426B2FD003A5E04 /* IPMutablePacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02282426B2FC003A5E04 /* IPMutablePacket.swift */; };
|
||||
54CA02992426B2FD003A5E04 /* TCPMutablePacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02292426B2FC003A5E04 /* TCPMutablePacket.swift */; };
|
||||
54CA029A2426B2FD003A5E04 /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA022B2426B2FC003A5E04 /* Observer.swift */; };
|
||||
54CA029C2426B2FD003A5E04 /* AdapterSocketEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA022E2426B2FC003A5E04 /* AdapterSocketEvent.swift */; };
|
||||
54CA029D2426B2FD003A5E04 /* ProxyServerEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA022F2426B2FC003A5E04 /* ProxyServerEvent.swift */; };
|
||||
@@ -102,8 +107,6 @@
|
||||
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 */; };
|
||||
54CA02AA2426B2FD003A5E04 /* SpeedAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA023F2426B2FC003A5E04 /* SpeedAdapterFactory.swift */; };
|
||||
54CA02AB2426B2FD003A5E04 /* ShadowsocksAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA02402426B2FC003A5E04 /* ShadowsocksAdapterFactory.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 */; };
|
||||
@@ -112,10 +115,6 @@
|
||||
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 */; };
|
||||
54CA02B42426B2FD003A5E04 /* StreamObfuscater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA024A2426B2FD003A5E04 /* StreamObfuscater.swift */; };
|
||||
54CA02B52426B2FD003A5E04 /* CryptoStreamProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA024B2426B2FD003A5E04 /* CryptoStreamProcessor.swift */; };
|
||||
54CA02B62426B2FD003A5E04 /* ProtocolObfuscater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA024C2426B2FD003A5E04 /* ProtocolObfuscater.swift */; };
|
||||
54CA02B72426B2FD003A5E04 /* ShadowsocksAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CA024D2426B2FD003A5E04 /* ShadowsocksAdapter.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 */; };
|
||||
@@ -152,6 +151,9 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
540C6456240D929300E948F9 /* EditableRows.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableRows.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -166,7 +168,11 @@
|
||||
543CDB2123EEE61900B7F323 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
543CDB2223EEE61900B7F323 /* GlassVPN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GlassVPN.entitlements; sourceTree = "<group>"; };
|
||||
544C95252407B1C700AB89D0 /* SharedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedState.swift; sourceTree = "<group>"; };
|
||||
54751E502423955000168273 /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = "<group>"; };
|
||||
5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCRecordingDetails.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -178,7 +184,7 @@
|
||||
54B345A5241BB982004C53CC /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
|
||||
54B345A8241BBA0B004C53CC /* Generic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generic.swift; sourceTree = "<group>"; };
|
||||
54B345AA241BBA5B004C53CC /* AlertSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertSheet.swift; sourceTree = "<group>"; };
|
||||
54B345AC241BBB00004C53CC /* GroupedDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupedDomain.swift; sourceTree = "<group>"; };
|
||||
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 /* SQDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQDB.swift; sourceTree = "<group>"; };
|
||||
54C056DA23E9E36E00214A3F /* AppInfoType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoType.swift; sourceTree = "<group>"; };
|
||||
@@ -234,8 +240,6 @@
|
||||
54CA02242426B2FC003A5E04 /* DNSEnums.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSEnums.swift; sourceTree = "<group>"; };
|
||||
54CA02262426B2FC003A5E04 /* PacketProtocolParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketProtocolParser.swift; sourceTree = "<group>"; };
|
||||
54CA02272426B2FC003A5E04 /* IPPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPPacket.swift; sourceTree = "<group>"; };
|
||||
54CA02282426B2FC003A5E04 /* IPMutablePacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IPMutablePacket.swift; sourceTree = "<group>"; };
|
||||
54CA02292426B2FC003A5E04 /* TCPMutablePacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TCPMutablePacket.swift; sourceTree = "<group>"; };
|
||||
54CA022B2426B2FC003A5E04 /* Observer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observer.swift; sourceTree = "<group>"; };
|
||||
54CA022E2426B2FC003A5E04 /* AdapterSocketEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdapterSocketEvent.swift; sourceTree = "<group>"; };
|
||||
54CA022F2426B2FC003A5E04 /* ProxyServerEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxyServerEvent.swift; sourceTree = "<group>"; };
|
||||
@@ -250,8 +254,6 @@
|
||||
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>"; };
|
||||
54CA023F2426B2FC003A5E04 /* SpeedAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpeedAdapterFactory.swift; sourceTree = "<group>"; };
|
||||
54CA02402426B2FC003A5E04 /* ShadowsocksAdapterFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsocksAdapterFactory.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>"; };
|
||||
@@ -260,10 +262,6 @@
|
||||
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>"; };
|
||||
54CA024A2426B2FD003A5E04 /* StreamObfuscater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamObfuscater.swift; sourceTree = "<group>"; };
|
||||
54CA024B2426B2FD003A5E04 /* CryptoStreamProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoStreamProcessor.swift; sourceTree = "<group>"; };
|
||||
54CA024C2426B2FD003A5E04 /* ProtocolObfuscater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProtocolObfuscater.swift; sourceTree = "<group>"; };
|
||||
54CA024D2426B2FD003A5E04 /* ShadowsocksAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsocksAdapter.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>"; };
|
||||
@@ -313,6 +311,17 @@
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
540E677E242D2CD200871BBE /* Recordings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
540E677F242D2CF100871BBE /* VCRecordings.swift */,
|
||||
540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */,
|
||||
540E67812433483D00871BBE /* VCEditRecording.swift */,
|
||||
5458EBBF243A3F2200CFEB15 /* TVCRecordingDetails.swift */,
|
||||
);
|
||||
path = Recordings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
541AC5CB2399498A00A769D7 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -337,11 +346,12 @@
|
||||
children = (
|
||||
54B3459A2415651C004C53CC /* DB */,
|
||||
54B345A4241BB975004C53CC /* Extensions */,
|
||||
545DDDD224436A03003B6544 /* Common Classes */,
|
||||
548B1F9423D338EC005B047C /* main.entitlements */,
|
||||
541AC5D72399498A00A769D7 /* AppDelegate.swift */,
|
||||
542E2A972404973F001462DC /* TBCMain.swift */,
|
||||
54B34597240F18DD004C53CC /* TVC Extensions */,
|
||||
540C6454240D5BAE00E948F9 /* Requests */,
|
||||
540E677E242D2CD200871BBE /* Recordings */,
|
||||
540C6455240D5BD200E948F9 /* Settings */,
|
||||
54B345B12422E029004C53CC /* unused */,
|
||||
541AC5DB2399498A00A769D7 /* Main.storyboard */,
|
||||
@@ -376,12 +386,14 @@
|
||||
path = GlassVPN;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54B34597240F18DD004C53CC /* TVC Extensions */ = {
|
||||
545DDDD224436A03003B6544 /* Common Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545DDDD024436983003B6544 /* QuickUI.swift */,
|
||||
545DDDCE243E6267003B6544 /* TutorialSheet.swift */,
|
||||
540C6456240D929300E948F9 /* EditableRows.swift */,
|
||||
);
|
||||
path = "TVC Extensions";
|
||||
path = "Common Classes";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54B3459A2415651C004C53CC /* DB */ = {
|
||||
@@ -399,10 +411,11 @@
|
||||
544C95252407B1C700AB89D0 /* SharedState.swift */,
|
||||
54B345A8241BBA0B004C53CC /* Generic.swift */,
|
||||
54B345A5241BB982004C53CC /* Notifications.swift */,
|
||||
54B345AC241BBB00004C53CC /* DBExtensions.swift */,
|
||||
54B345AA241BBA5B004C53CC /* AlertSheet.swift */,
|
||||
54B345AC241BBB00004C53CC /* GroupedDomain.swift */,
|
||||
54B34595240F0513004C53CC /* TableView.swift */,
|
||||
54751E502423955000168273 /* FileManager.swift */,
|
||||
54751E502423955000168273 /* URL.swift */,
|
||||
545DDDD324466D37003B6544 /* AutoLayout.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -562,8 +575,6 @@
|
||||
children = (
|
||||
54CA02262426B2FC003A5E04 /* PacketProtocolParser.swift */,
|
||||
54CA02272426B2FC003A5E04 /* IPPacket.swift */,
|
||||
54CA02282426B2FC003A5E04 /* IPMutablePacket.swift */,
|
||||
54CA02292426B2FC003A5E04 /* TCPMutablePacket.swift */,
|
||||
);
|
||||
path = Packet;
|
||||
sourceTree = "<group>";
|
||||
@@ -611,7 +622,6 @@
|
||||
54CA023C2426B2FC003A5E04 /* SOCKS5Adapter.swift */,
|
||||
54CA023D2426B2FC003A5E04 /* RejectAdapter.swift */,
|
||||
54CA023E2426B2FC003A5E04 /* Factory */,
|
||||
54CA02492426B2FD003A5E04 /* Shadowsocks */,
|
||||
);
|
||||
path = AdapterSocket;
|
||||
sourceTree = "<group>";
|
||||
@@ -619,8 +629,6 @@
|
||||
54CA023E2426B2FC003A5E04 /* Factory */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54CA023F2426B2FC003A5E04 /* SpeedAdapterFactory.swift */,
|
||||
54CA02402426B2FC003A5E04 /* ShadowsocksAdapterFactory.swift */,
|
||||
54CA02412426B2FC003A5E04 /* AuthenticationServerAdapterFactory.swift */,
|
||||
54CA02422426B2FC003A5E04 /* RejectAdapterFactory.swift */,
|
||||
54CA02432426B2FD003A5E04 /* AdapterFactory.swift */,
|
||||
@@ -633,17 +641,6 @@
|
||||
path = Factory;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54CA02492426B2FD003A5E04 /* Shadowsocks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54CA024A2426B2FD003A5E04 /* StreamObfuscater.swift */,
|
||||
54CA024B2426B2FD003A5E04 /* CryptoStreamProcessor.swift */,
|
||||
54CA024C2426B2FD003A5E04 /* ProtocolObfuscater.swift */,
|
||||
54CA024D2426B2FD003A5E04 /* ShadowsocksAdapter.swift */,
|
||||
);
|
||||
path = Shadowsocks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54CA024E2426B2FD003A5E04 /* ProxySocket */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -772,24 +769,31 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
54B345AD241BBB00004C53CC /* GroupedDomain.swift in Sources */,
|
||||
545DDDD424466D37003B6544 /* AutoLayout.swift in Sources */,
|
||||
54B345AD241BBB00004C53CC /* DBExtensions.swift in Sources */,
|
||||
540E67842433FAFE00871BBE /* TVCPreviousRecords.swift in Sources */,
|
||||
54B345A6241BB982004C53CC /* Notifications.swift in Sources */,
|
||||
54B345AB241BBA5B004C53CC /* AlertSheet.swift in Sources */,
|
||||
544C95262407B1C700AB89D0 /* SharedState.swift in Sources */,
|
||||
545DDDCF243E6267003B6544 /* TutorialSheet.swift in Sources */,
|
||||
54B345A9241BBA0B004C53CC /* Generic.swift in Sources */,
|
||||
54B34596240F0513004C53CC /* TableView.swift in Sources */,
|
||||
540E6780242D2CF100871BBE /* VCRecordings.swift in Sources */,
|
||||
54953E3323DC752E0054345C /* SQDB.swift in Sources */,
|
||||
54953E6123E0D69A0054345C /* TVCHosts.swift in Sources */,
|
||||
540C6457240D929300E948F9 /* EditableRows.swift in Sources */,
|
||||
54751E512423955100168273 /* FileManager.swift in Sources */,
|
||||
54751E512423955100168273 /* URL.swift in Sources */,
|
||||
542E2A9A24051556001462DC /* TVCSettings.swift in Sources */,
|
||||
54953E5F23DEBE840054345C /* TVCDomains.swift in Sources */,
|
||||
54C056DB23E9E36E00214A3F /* AppInfoType.swift in Sources */,
|
||||
54953E6F23E44CD00054345C /* TVCHostDetails.swift in Sources */,
|
||||
54C056DD23E9EEF700214A3F /* BundleIcon.swift in Sources */,
|
||||
542E2A982404973F001462DC /* TBCMain.swift in Sources */,
|
||||
5458EBC0243A3F2200CFEB15 /* TVCRecordingDetails.swift in Sources */,
|
||||
545DDDD124436983003B6544 /* QuickUI.swift in Sources */,
|
||||
541AC5D82399498A00A769D7 /* AppDelegate.swift in Sources */,
|
||||
54B345992414F491004C53CC /* DBWrapper.swift in Sources */,
|
||||
540E67822433483D00871BBE /* VCEditRecording.swift in Sources */,
|
||||
54B34594240E6343004C53CC /* TVCFilter.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -802,7 +806,6 @@
|
||||
54CA025D2426B2FD003A5E04 /* HTTPHeader.swift in Sources */,
|
||||
54CA02832426B2FD003A5E04 /* DNSSessionMatchResult.swift in Sources */,
|
||||
54CA02862426B2FD003A5E04 /* RuleManager.swift in Sources */,
|
||||
54CA02B52426B2FD003A5E04 /* CryptoStreamProcessor.swift in Sources */,
|
||||
54CA02B82426B2FD003A5E04 /* HTTPProxySocket.swift in Sources */,
|
||||
54CA02C32426DCCD003A5E04 /* GCDAsyncSocket.m in Sources */,
|
||||
54CA02752426B2FD003A5E04 /* IPRange.swift in Sources */,
|
||||
@@ -812,7 +815,6 @@
|
||||
54CA02C42426DCCD003A5E04 /* GCDAsyncUdpSocket.m in Sources */,
|
||||
54CA02BA2426B2FD003A5E04 /* ProxySocket.swift in Sources */,
|
||||
54CA025E2426B2FD003A5E04 /* ResponseGeneratorFactory.swift in Sources */,
|
||||
54CA02982426B2FD003A5E04 /* IPMutablePacket.swift in Sources */,
|
||||
54CA02892426B2FD003A5E04 /* Tunnel.swift in Sources */,
|
||||
54CA029F2426B2FD003A5E04 /* ProxySocketEvent.swift in Sources */,
|
||||
54CA027D2426B2FD003A5E04 /* GlobalIntializer.swift in Sources */,
|
||||
@@ -823,10 +825,8 @@
|
||||
54CA029E2426B2FD003A5E04 /* EventType.swift in Sources */,
|
||||
54CA02912426B2FD003A5E04 /* DNSMessage.swift in Sources */,
|
||||
54CA02712426B2FD003A5E04 /* UInt128.swift in Sources */,
|
||||
54CA02B62426B2FD003A5E04 /* ProtocolObfuscater.swift in Sources */,
|
||||
54CA02882426B2FD003A5E04 /* QueueFactory.swift in Sources */,
|
||||
54CA02A12426B2FD003A5E04 /* RuleMatchEvent.swift in Sources */,
|
||||
54CA02AB2426B2FD003A5E04 /* ShadowsocksAdapterFactory.swift in Sources */,
|
||||
54CA02BE2426D4F3003A5E04 /* DDLog.swift in Sources */,
|
||||
54CA02962426B2FD003A5E04 /* PacketProtocolParser.swift in Sources */,
|
||||
54CA02932426B2FD003A5E04 /* DNSServer.swift in Sources */,
|
||||
@@ -840,7 +840,6 @@
|
||||
54CA026B2426B2FD003A5E04 /* GCDTCPSocket.swift in Sources */,
|
||||
54CA028E2426B2FD003A5E04 /* IPStackProtocol.swift in Sources */,
|
||||
54CA01D52426B252003A5E04 /* SafeDict.swift in Sources */,
|
||||
54CA02992426B2FD003A5E04 /* TCPMutablePacket.swift in Sources */,
|
||||
54CA027B2426B2FD003A5E04 /* HTTPAuthentication.swift in Sources */,
|
||||
54CA02762426B2FD003A5E04 /* IPAddress.swift in Sources */,
|
||||
54CA02B02426B2FD003A5E04 /* SecureHTTPAdapterFactory.swift in Sources */,
|
||||
@@ -852,14 +851,12 @@
|
||||
54CA025F2426B2FD003A5E04 /* ProxyServer.swift in Sources */,
|
||||
54CA02842426B2FD003A5E04 /* Rule.swift in Sources */,
|
||||
54CA02B92426B2FD003A5E04 /* DirectProxySocket.swift in Sources */,
|
||||
54751E522423955100168273 /* FileManager.swift in Sources */,
|
||||
54751E522423955100168273 /* URL.swift in Sources */,
|
||||
54CA02A92426B2FD003A5E04 /* RejectAdapter.swift in Sources */,
|
||||
54CA02732426B2FD003A5E04 /* IPPool.swift in Sources */,
|
||||
54CA027E2426B2FD003A5E04 /* DomainListRule.swift in Sources */,
|
||||
54CA02782426B2FD003A5E04 /* BinaryDataScanner.swift in Sources */,
|
||||
54CA02B12426B2FD003A5E04 /* ServerAdapterFactory.swift in Sources */,
|
||||
54CA02B42426B2FD003A5E04 /* StreamObfuscater.swift in Sources */,
|
||||
54CA02AA2426B2FD003A5E04 /* SpeedAdapterFactory.swift in Sources */,
|
||||
54CA02952426B2FD003A5E04 /* DNSEnums.swift in Sources */,
|
||||
54CA02802426B2FD003A5E04 /* DNSSessionMatchType.swift in Sources */,
|
||||
54CA02A22426B2FD003A5E04 /* ObserverFactory.swift in Sources */,
|
||||
@@ -883,7 +880,6 @@
|
||||
546063E523FEFAFE008F505A /* SQDB.swift in Sources */,
|
||||
54CA02872426B2FD003A5E04 /* IPRangeListRule.swift in Sources */,
|
||||
54CA02922426B2FD003A5E04 /* DNSSession.swift in Sources */,
|
||||
54CA02B72426B2FD003A5E04 /* ShadowsocksAdapter.swift in Sources */,
|
||||
54CA026D2426B2FD003A5E04 /* Opt.swift in Sources */,
|
||||
54CA02B32426B2FD003A5E04 /* HTTPAdapterFactory.swift in Sources */,
|
||||
54CA02702426B2FD003A5E04 /* HTTPStreamScanner.swift in Sources */,
|
||||
@@ -1049,7 +1045,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
CURRENT_PROJECT_VERSION = 15;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1068,7 +1064,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
CURRENT_PROJECT_VERSION = 15;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1087,7 +1083,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
CURRENT_PROJECT_VERSION = 15;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
@@ -1105,7 +1101,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
CURRENT_PROJECT_VERSION = 15;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
//import Foundation
|
||||
//
|
||||
//enum ChangeType {
|
||||
// case Address, Port
|
||||
//}
|
||||
//
|
||||
//public class IPMutablePacket {
|
||||
// // Support only IPv4 for now
|
||||
//
|
||||
// let version: IPVersion
|
||||
// let proto: TransportType
|
||||
// let IPHeaderLength: Int
|
||||
// var sourceAddress: IPv4Address {
|
||||
// get {
|
||||
// return IPv4Address(fromBytesInNetworkOrder: payload.bytes.advancedBy(12))
|
||||
// }
|
||||
// set {
|
||||
// setIPv4Address(sourceAddress, newAddress: newValue, at: 12)
|
||||
// }
|
||||
// }
|
||||
// var destinationAddress: IPv4Address {
|
||||
// get {
|
||||
// return IPv4Address(fromBytesInNetworkOrder: payload.bytes.advancedBy(16))
|
||||
// }
|
||||
// set {
|
||||
// setIPv4Address(destinationAddress, newAddress: newValue, at: 16)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let payload: NSMutableData
|
||||
//
|
||||
// public init(payload: NSData) {
|
||||
// let vl = UnsafePointer<UInt8>(payload.bytes).memory
|
||||
// version = IPVersion(rawValue: vl >> 4)!
|
||||
// IPHeaderLength = Int(vl & 0x0F) * 4
|
||||
// let p = UnsafePointer<UInt8>(payload.bytes.advancedBy(9)).memory
|
||||
// proto = TransportType(rawValue: p)!
|
||||
// self.payload = NSMutableData(data: payload)
|
||||
// }
|
||||
//
|
||||
// func updateChecksum(oldValue: UInt16, newValue: UInt16, type: ChangeType) {
|
||||
// if type == .Address {
|
||||
// updateChecksum(oldValue, newValue: newValue, at: 10)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // swiftlint:disable:next variable_name
|
||||
// internal func updateChecksum(oldValue: UInt16, newValue: UInt16, at: Int) {
|
||||
// let oldChecksum = UnsafePointer<UInt16>(payload.bytes.advancedBy(at)).memory
|
||||
// let oc32 = UInt32(~oldChecksum)
|
||||
// let ov32 = UInt32(~oldValue)
|
||||
// let nv32 = UInt32(newValue)
|
||||
// var newChecksum32 = oc32 &+ ov32 &+ nv32
|
||||
// newChecksum32 = (newChecksum32 & 0xFFFF) + (newChecksum32 >> 16)
|
||||
// newChecksum32 = (newChecksum32 & 0xFFFF) &+ (newChecksum32 >> 16)
|
||||
// var newChecksum = ~UInt16(newChecksum32)
|
||||
// payload.replaceBytesInRange(NSRange(location: at, length: 2), withBytes: &newChecksum, length: 2)
|
||||
// }
|
||||
//
|
||||
// // swiftlint:disable:next variable_name
|
||||
// private func foldChecksum(checksum: UInt32) -> UInt32 {
|
||||
// var checksum = checksum
|
||||
// while checksum > 0xFFFF {
|
||||
// checksum = (checksum & 0xFFFF) + (checksum >> 16)
|
||||
// }
|
||||
// return checksum
|
||||
// }
|
||||
//
|
||||
// // swiftlint:disable:next variable_name
|
||||
// private func setIPv4Address(oldAddress: IPv4Address, newAddress: IPv4Address, at: Int) {
|
||||
// payload.replaceBytesInRange(NSRange(location: at, length: 4), withBytes: newAddress.bytesInNetworkOrder, length: 4)
|
||||
// updateChecksum(UnsafePointer<UInt16>(oldAddress.bytesInNetworkOrder).memory, newValue: UnsafePointer<UInt16>(newAddress.bytesInNetworkOrder).memory, type: .Address)
|
||||
// updateChecksum(UnsafePointer<UInt16>(oldAddress.bytesInNetworkOrder).advancedBy(1).memory, newValue: UnsafePointer<UInt16>(newAddress.bytesInNetworkOrder).advancedBy(1).memory, type: .Address)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -1,32 +0,0 @@
|
||||
//import Foundation
|
||||
//
|
||||
//class TCPMutablePacket: IPMutablePacket {
|
||||
// var sourcePort: Port {
|
||||
// get {
|
||||
// return Port(bytesInNetworkOrder: payload.bytes.advancedBy(IPHeaderLength))
|
||||
// }
|
||||
// set {
|
||||
// setPort(sourcePort, newPort: newValue, at: 0)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var destinationPort: Port {
|
||||
// get {
|
||||
// return Port(bytesInNetworkOrder: payload.bytes.advancedBy(IPHeaderLength + 2))
|
||||
// }
|
||||
// set {
|
||||
// setPort(destinationPort, newPort: newValue, at: 2)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override func updateChecksum(oldValue: UInt16, newValue: UInt16, type: ChangeType) {
|
||||
// super.updateChecksum(oldValue, newValue: newValue, type: type)
|
||||
// updateChecksum(oldValue, newValue: newValue, at: IPHeaderLength + 16)
|
||||
// }
|
||||
//
|
||||
// // swiftlint:disable:next variable_name
|
||||
// private func setPort(oldPort: Port, newPort: Port, at: Int) {
|
||||
// payload.replaceBytesInRange(NSRange(location: at + IPHeaderLength, length: 2), withBytes: newPort.bytesInNetworkOrder, length: 2)
|
||||
// updateChecksum(oldPort.valueInNetworkOrder, newValue: newPort.valueInNetworkOrder, type: .Port)
|
||||
// }
|
||||
//}
|
||||
@@ -1,28 +0,0 @@
|
||||
//import Foundation
|
||||
//
|
||||
///// Factory building Shadowsocks adapter.
|
||||
//open class ShadowsocksAdapterFactory: ServerAdapterFactory {
|
||||
// let protocolObfuscaterFactory: ShadowsocksAdapter.ProtocolObfuscater.Factory
|
||||
// let cryptorFactory: ShadowsocksAdapter.CryptoStreamProcessor.Factory
|
||||
// let streamObfuscaterFactory: ShadowsocksAdapter.StreamObfuscater.Factory
|
||||
//
|
||||
// public init(serverHost: String, serverPort: Int, protocolObfuscaterFactory: ShadowsocksAdapter.ProtocolObfuscater.Factory, cryptorFactory: ShadowsocksAdapter.CryptoStreamProcessor.Factory, streamObfuscaterFactory: ShadowsocksAdapter.StreamObfuscater.Factory) {
|
||||
// self.protocolObfuscaterFactory = protocolObfuscaterFactory
|
||||
// self.cryptorFactory = cryptorFactory
|
||||
// self.streamObfuscaterFactory = streamObfuscaterFactory
|
||||
// super.init(serverHost: serverHost, serverPort: serverPort)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// Get a Shadowsocks adapter.
|
||||
//
|
||||
// - parameter session: The connect session.
|
||||
//
|
||||
// - returns: The built adapter.
|
||||
// */
|
||||
// override open func getAdapterFor(session: ConnectSession) -> AdapterSocket {
|
||||
// let adapter = ShadowsocksAdapter(host: serverHost, port: serverPort, protocolObfuscater: protocolObfuscaterFactory.build(), cryptor: cryptorFactory.build(), streamObfuscator: streamObfuscaterFactory.build(for: session))
|
||||
// adapter.socket = RawSocketFactory.getRawSocket()
|
||||
// return adapter
|
||||
// }
|
||||
//}
|
||||
@@ -1,26 +0,0 @@
|
||||
//import Foundation
|
||||
//
|
||||
///// Factory building speed adapter.
|
||||
//open class SpeedAdapterFactory: AdapterFactory {
|
||||
// open var adapterFactories: [(AdapterFactory, Int)]!
|
||||
//
|
||||
// public override init() {}
|
||||
//
|
||||
// /**
|
||||
// Get a speed adapter.
|
||||
//
|
||||
// - parameter session: The connect session.
|
||||
//
|
||||
// - returns: The built adapter.
|
||||
// */
|
||||
// override open func getAdapterFor(session: ConnectSession) -> AdapterSocket {
|
||||
// let adapters = adapterFactories.map { adapterFactory, delay -> (AdapterSocket, Int) in
|
||||
// let adapter = adapterFactory.getAdapterFor(session: session)
|
||||
// adapter.socket = RawSocketFactory.getRawSocket()
|
||||
// return (adapter, delay)
|
||||
// }
|
||||
// let speedAdapter = SpeedAdapter()
|
||||
// speedAdapter.adapters = adapters
|
||||
// return speedAdapter
|
||||
// }
|
||||
//}
|
||||
@@ -14,7 +14,7 @@ public class SOCKS5Adapter: AdapterSocket {
|
||||
|
||||
var internalStatus: SOCKS5AdapterStatus = .invalid
|
||||
|
||||
let helloData = Data(bytes: UnsafePointer<UInt8>(([0x05, 0x01, 0x00] as [UInt8])), count: 3)
|
||||
let helloData = Data([0x05, 0x01, 0x00])
|
||||
|
||||
public enum ReadTag: Int {
|
||||
case methodResponse = -20000, connectResponseFirstPart, connectResponseSecondPart
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
//import Foundation
|
||||
//
|
||||
//extension ShadowsocksAdapter {
|
||||
// public class CryptoStreamProcessor {
|
||||
// public class Factory {
|
||||
// let password: String
|
||||
// let algorithm: CryptoAlgorithm
|
||||
// let key: Data
|
||||
//
|
||||
// public init(password: String, algorithm: CryptoAlgorithm) {
|
||||
// self.password = password
|
||||
// self.algorithm = algorithm
|
||||
// key = CryptoHelper.getKey(password, methodType: algorithm)
|
||||
// }
|
||||
//
|
||||
// public func build() -> CryptoStreamProcessor {
|
||||
// return CryptoStreamProcessor(key: key, algorithm: algorithm)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public weak var inputStreamProcessor: StreamObfuscater.StreamObfuscaterBase!
|
||||
// public weak var outputStreamProcessor: ProtocolObfuscater.ProtocolObfuscaterBase!
|
||||
//
|
||||
// var readIV: Data!
|
||||
// let key: Data
|
||||
// let algorithm: CryptoAlgorithm
|
||||
//
|
||||
// var sendKey = false
|
||||
//
|
||||
// var buffer = Buffer(capacity: 0)
|
||||
//
|
||||
// lazy var writeIV: Data = {
|
||||
// [unowned self] in
|
||||
// CryptoHelper.getIV(self.algorithm)
|
||||
// }()
|
||||
// lazy var ivLength: Int = {
|
||||
// [unowned self] in
|
||||
// CryptoHelper.getIVLength(self.algorithm)
|
||||
// }()
|
||||
// lazy var encryptor: StreamCryptoProtocol = {
|
||||
// [unowned self] in
|
||||
// self.getCrypto(.encrypt)
|
||||
// }()
|
||||
// lazy var decryptor: StreamCryptoProtocol = {
|
||||
// [unowned self] in
|
||||
// self.getCrypto(.decrypt)
|
||||
// }()
|
||||
//
|
||||
// init(key: Data, algorithm: CryptoAlgorithm) {
|
||||
// self.key = key
|
||||
// self.algorithm = algorithm
|
||||
// }
|
||||
//
|
||||
// func encrypt(data: inout Data) {
|
||||
// return encryptor.update(&data)
|
||||
// }
|
||||
//
|
||||
// func decrypt(data: inout Data) {
|
||||
// return decryptor.update(&data)
|
||||
// }
|
||||
//
|
||||
// public func input(data: Data) throws {
|
||||
// var data = data
|
||||
//
|
||||
// if readIV == nil {
|
||||
// buffer.append(data: data)
|
||||
// readIV = buffer.get(length: ivLength)
|
||||
// guard readIV != nil else {
|
||||
// try inputStreamProcessor!.input(data: Data())
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// data = buffer.get() ?? Data()
|
||||
// buffer.release()
|
||||
// }
|
||||
//
|
||||
// decrypt(data: &data)
|
||||
// try inputStreamProcessor!.input(data: data)
|
||||
// }
|
||||
//
|
||||
// public func output(data: Data) {
|
||||
// var data = data
|
||||
// encrypt(data: &data)
|
||||
// if sendKey {
|
||||
// return outputStreamProcessor!.output(data: data)
|
||||
// } else {
|
||||
// sendKey = true
|
||||
// var out = Data(capacity: data.count + writeIV.count)
|
||||
// out.append(writeIV)
|
||||
// out.append(data)
|
||||
//
|
||||
// return outputStreamProcessor!.output(data: out)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private func getCrypto(_ operation: CryptoOperation) -> StreamCryptoProtocol {
|
||||
// switch algorithm {
|
||||
// case .AES128CFB, .AES192CFB, .AES256CFB:
|
||||
// switch operation {
|
||||
// case .decrypt:
|
||||
// return CCCrypto(operation: .decrypt, mode: .cfb, algorithm: .aes, initialVector: readIV, key: key)
|
||||
// case .encrypt:
|
||||
// return CCCrypto(operation: .encrypt, mode: .cfb, algorithm: .aes, initialVector: writeIV, key: key)
|
||||
// }
|
||||
// case .CHACHA20:
|
||||
// switch operation {
|
||||
// case .decrypt:
|
||||
// return SodiumStreamCrypto(key: key, iv: readIV, algorithm: .chacha20)
|
||||
// case .encrypt:
|
||||
// return SodiumStreamCrypto(key: key, iv: writeIV, algorithm: .chacha20)
|
||||
// }
|
||||
// case .SALSA20:
|
||||
// switch operation {
|
||||
// case .decrypt:
|
||||
// return SodiumStreamCrypto(key: key, iv: readIV, algorithm: .salsa20)
|
||||
// case .encrypt:
|
||||
// return SodiumStreamCrypto(key: key, iv: writeIV, algorithm: .salsa20)
|
||||
// }
|
||||
// case .RC4MD5:
|
||||
// var combinedKey = Data(capacity: key.count + ivLength)
|
||||
// combinedKey.append(key)
|
||||
// switch operation {
|
||||
// case .decrypt:
|
||||
// combinedKey.append(readIV)
|
||||
// return CCCrypto(operation: .decrypt, mode: .rc4, algorithm: .rc4, initialVector: nil, key: MD5Hash.final(combinedKey))
|
||||
// case .encrypt:
|
||||
// combinedKey.append(writeIV)
|
||||
// return CCCrypto(operation: .encrypt, mode: .rc4, algorithm: .rc4, initialVector: nil, key: MD5Hash.final(combinedKey))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -1,371 +0,0 @@
|
||||
//import Foundation
|
||||
//
|
||||
//extension ShadowsocksAdapter {
|
||||
// public struct ProtocolObfuscater {
|
||||
// public class Factory {
|
||||
// public init() {}
|
||||
//
|
||||
// public func build() -> ProtocolObfuscaterBase {
|
||||
// return ProtocolObfuscaterBase()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public class ProtocolObfuscaterBase {
|
||||
// public weak var inputStreamProcessor: CryptoStreamProcessor!
|
||||
// public weak var outputStreamProcessor: ShadowsocksAdapter!
|
||||
//
|
||||
// public func start() {}
|
||||
// public func input(data: Data) throws {}
|
||||
// public func output(data: Data) {}
|
||||
//
|
||||
// public func didWrite() {}
|
||||
// }
|
||||
//
|
||||
// public class OriginProtocolObfuscater: ProtocolObfuscaterBase {
|
||||
//
|
||||
// public class Factory: ProtocolObfuscater.Factory {
|
||||
// public override init() {}
|
||||
//
|
||||
// public override func build() -> ShadowsocksAdapter.ProtocolObfuscater.ProtocolObfuscaterBase {
|
||||
// return OriginProtocolObfuscater()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public override func start() {
|
||||
// outputStreamProcessor.becomeReadyToForward()
|
||||
// }
|
||||
//
|
||||
// public override func input(data: Data) throws {
|
||||
// try inputStreamProcessor.input(data: data)
|
||||
// }
|
||||
//
|
||||
// public override func output(data: Data) {
|
||||
// outputStreamProcessor.output(data: data)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public class HTTPProtocolObfuscater: ProtocolObfuscaterBase {
|
||||
//
|
||||
// public class Factory: ProtocolObfuscater.Factory {
|
||||
// let method: String
|
||||
// let hosts: [String]
|
||||
// let customHeader: String?
|
||||
//
|
||||
// public init(method: String = "GET", hosts: [String], customHeader: String?) {
|
||||
// self.method = method
|
||||
// self.hosts = hosts
|
||||
// self.customHeader = customHeader
|
||||
// }
|
||||
//
|
||||
// public override func build() -> ShadowsocksAdapter.ProtocolObfuscater.ProtocolObfuscaterBase {
|
||||
// return HTTPProtocolObfuscater(method: method, hosts: hosts, customHeader: customHeader)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// static let headerLength = 30
|
||||
// static let userAgent = ["Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0",
|
||||
// "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/44.0",
|
||||
// "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
|
||||
// "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36",
|
||||
// "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0",
|
||||
// "Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)",
|
||||
// "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27",
|
||||
// "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)",
|
||||
// "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
|
||||
// "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/BuildID) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36",
|
||||
// "Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
|
||||
// "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"]
|
||||
//
|
||||
// let method: String
|
||||
// let hosts: [String]
|
||||
// let customHeader: String?
|
||||
//
|
||||
// var readingFakeHeader = false
|
||||
// var sendHeader = false
|
||||
// var remaining = false
|
||||
//
|
||||
// var buffer = Buffer(capacity: 8192)
|
||||
//
|
||||
// public init(method: String = "GET", hosts: [String], customHeader: String?) {
|
||||
// self.method = method
|
||||
// self.hosts = hosts
|
||||
// self.customHeader = customHeader
|
||||
// }
|
||||
//
|
||||
// private func generateHeader(encapsulating data: Data) -> String {
|
||||
// let ind = Int(arc4random_uniform(UInt32(hosts.count)))
|
||||
// let host = outputStreamProcessor.port == 80 ? hosts[ind] : "\(hosts[ind]):\(outputStreamProcessor.port)"
|
||||
// var header = "\(method) /\(hexlify(data: data)) HTTP/1.1\r\nHost: \(host)\r\n"
|
||||
// if let customHeader = customHeader {
|
||||
// header += customHeader
|
||||
// } else {
|
||||
// let ind = Int(arc4random_uniform(UInt32(HTTPProtocolObfuscater.userAgent.count)))
|
||||
// header += "User-Agent: \(HTTPProtocolObfuscater.userAgent[ind])\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\nDNT: 1\r\nConnection: keep-alive"
|
||||
// }
|
||||
// header += "\r\n\r\n"
|
||||
// return header
|
||||
// }
|
||||
//
|
||||
// private func hexlify(data: Data) -> String {
|
||||
// var result = ""
|
||||
// for i in data {
|
||||
// result = result.appendingFormat("%%%02x", i)
|
||||
// }
|
||||
// return result
|
||||
// }
|
||||
//
|
||||
// public override func start() {
|
||||
// readingFakeHeader = true
|
||||
// outputStreamProcessor.becomeReadyToForward()
|
||||
// }
|
||||
//
|
||||
// public override func input(data: Data) throws {
|
||||
// if readingFakeHeader {
|
||||
// buffer.append(data: data)
|
||||
// if buffer.get(to: Utils.HTTPData.DoubleCRLF) != nil {
|
||||
// readingFakeHeader = false
|
||||
// if let remainData = buffer.get() {
|
||||
// try inputStreamProcessor.input(data: remainData)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// try inputStreamProcessor.input(data: Data())
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// try inputStreamProcessor.input(data: data)
|
||||
// }
|
||||
//
|
||||
// public override func output(data: Data) {
|
||||
// if sendHeader {
|
||||
// outputStreamProcessor.output(data: data)
|
||||
// } else {
|
||||
// var fakeRequestDataLength = inputStreamProcessor.key.count + HTTPProtocolObfuscater.headerLength
|
||||
// if data.count - fakeRequestDataLength > 64 {
|
||||
// fakeRequestDataLength += Int(arc4random_uniform(64))
|
||||
// } else {
|
||||
// fakeRequestDataLength = data.count
|
||||
// }
|
||||
//
|
||||
// var outputData = generateHeader(encapsulating: data.subdata(in: 0 ..< fakeRequestDataLength)).data(using: .utf8)!
|
||||
// outputData.append(data.subdata(in: fakeRequestDataLength ..< data.count))
|
||||
// sendHeader = true
|
||||
// outputStreamProcessor.output(data: outputData)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public class TLSProtocolObfuscater: ProtocolObfuscaterBase {
|
||||
//
|
||||
// public class Factory: ProtocolObfuscater.Factory {
|
||||
// let hosts: [String]
|
||||
//
|
||||
// public init(hosts: [String]) {
|
||||
// self.hosts = hosts
|
||||
// }
|
||||
//
|
||||
// public override func build() -> ShadowsocksAdapter.ProtocolObfuscater.ProtocolObfuscaterBase {
|
||||
// return TLSProtocolObfuscater(hosts: hosts)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let hosts: [String]
|
||||
// let clientID: Data = {
|
||||
// var id = Data(count: 32)
|
||||
// Utils.Random.fill(data: &id)
|
||||
// return id
|
||||
// }()
|
||||
//
|
||||
// private var status = 0
|
||||
//
|
||||
// private var buffer = Buffer(capacity: 1024)
|
||||
//
|
||||
// init(hosts: [String]) {
|
||||
// self.hosts = hosts
|
||||
// }
|
||||
//
|
||||
// public override func start() {
|
||||
// handleStatus0()
|
||||
// outputStreamProcessor.socket.readDataTo(length: 129)
|
||||
// }
|
||||
//
|
||||
// public override func input(data: Data) throws {
|
||||
// switch status {
|
||||
// case 8:
|
||||
// try handleInput(data: data)
|
||||
// case 1:
|
||||
// outputStreamProcessor.becomeReadyToForward()
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public override func output(data: Data) {
|
||||
// switch status {
|
||||
// case 8:
|
||||
// handleStatus8(data: data)
|
||||
// return
|
||||
// case 1:
|
||||
// handleStatus1(data: data)
|
||||
// return
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private func authData() -> Data {
|
||||
// var time = UInt32(Date.init().timeIntervalSince1970).bigEndian
|
||||
// var output = Data(count: 32)
|
||||
// var key = inputStreamProcessor.key
|
||||
// key.append(clientID)
|
||||
//
|
||||
// withUnsafeBytes(of: &time) {
|
||||
// output.replaceSubrange(0 ..< 4, with: $0)
|
||||
// }
|
||||
//
|
||||
// Utils.Random.fill(data: &output, from: 4, length: 18)
|
||||
// output.withUnsafeBytes {
|
||||
// output.replaceSubrange(22 ..< 32, with: HMAC.final(value: $0.baseAddress!, length: 22, algorithm: .SHA1, key: key).subdata(in: 0..<10))
|
||||
// }
|
||||
// return output
|
||||
// }
|
||||
//
|
||||
// private func pack(data: Data) -> Data {
|
||||
// var output = Data()
|
||||
// var left = data.count
|
||||
// while left > 0 {
|
||||
// let blockSize = UInt16(min(Int(arc4random_uniform(UInt32(UInt16.max))) % 4096 + 100, left))
|
||||
// var blockSizeBE = blockSize.bigEndian
|
||||
// output.append(contentsOf: [0x17, 0x03, 0x03])
|
||||
// withUnsafeBytes(of: &blockSizeBE) {
|
||||
// output.append($0.baseAddress!.assumingMemoryBound(to: UInt8.self), count: $0.count)
|
||||
// }
|
||||
// output.append(data.subdata(in: data.count - left ..< data.count - left + Int(blockSize)))
|
||||
// left -= Int(blockSize)
|
||||
// }
|
||||
// return output
|
||||
// }
|
||||
//
|
||||
// private func handleStatus8(data: Data) {
|
||||
// outputStreamProcessor.output(data: pack(data: data))
|
||||
// }
|
||||
//
|
||||
// private func handleStatus0() {
|
||||
// status = 1
|
||||
//
|
||||
// var outData = Data()
|
||||
// outData.append(contentsOf: [0x03, 0x03])
|
||||
// outData.append(authData())
|
||||
// outData.append(0x20)
|
||||
// outData.append(clientID)
|
||||
// outData.append(contentsOf: [0x00, 0x1c, 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x9c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0x0a])
|
||||
// outData.append("0100".data(using: .utf8)!)
|
||||
//
|
||||
// var extData = Data()
|
||||
// extData.append(contentsOf: [0xff, 0x01, 0x00, 0x01, 0x00])
|
||||
// let hostData = hosts[Int(arc4random_uniform(UInt32(hosts.count)))].data(using: .utf8)!
|
||||
//
|
||||
// var sniData = Data(capacity: hosts.count + 2 + 1 + 2 + 2 + 2)
|
||||
//
|
||||
// sniData.append(contentsOf: [0x00, 0x00])
|
||||
//
|
||||
// var _lenBE = UInt16(hostData.count + 5).bigEndian
|
||||
// withUnsafeBytes(of: &_lenBE) {
|
||||
// sniData.append($0.baseAddress!.assumingMemoryBound(to: UInt8.self), count: $0.count)
|
||||
// }
|
||||
//
|
||||
// _lenBE = UInt16(hostData.count + 3).bigEndian
|
||||
// withUnsafeBytes(of: &_lenBE) {
|
||||
// sniData.append($0.baseAddress!.assumingMemoryBound(to: UInt8.self), count: $0.count)
|
||||
// }
|
||||
//
|
||||
// sniData.append(0x00)
|
||||
//
|
||||
// _lenBE = UInt16(hostData.count).bigEndian
|
||||
// withUnsafeBytes(of: &_lenBE) {
|
||||
// sniData.append($0.baseAddress!.assumingMemoryBound(to: UInt8.self), count: $0.count)
|
||||
// }
|
||||
//
|
||||
// sniData.append(hostData)
|
||||
//
|
||||
// extData.append(sniData)
|
||||
//
|
||||
// extData.append(contentsOf: [0x00, 0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0xd0])
|
||||
//
|
||||
// var randomData = Data(count: 208)
|
||||
// Utils.Random.fill(data: &randomData)
|
||||
// extData.append(randomData)
|
||||
//
|
||||
// extData.append(contentsOf: [0x00, 0x0d, 0x00, 0x16, 0x00, 0x14, 0x06, 0x01, 0x06, 0x03, 0x05, 0x01, 0x05, 0x03, 0x04, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x03, 0x02, 0x01, 0x02, 0x03])
|
||||
// extData.append(contentsOf: [0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00])
|
||||
// extData.append(contentsOf: [0x00, 0x12, 0x00, 0x00])
|
||||
// extData.append(contentsOf: [0x75, 0x50, 0x00, 0x00])
|
||||
// extData.append(contentsOf: [0x00, 0x0b, 0x00, 0x02, 0x01, 0x00])
|
||||
// extData.append(contentsOf: [0x00, 0x0a, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, 0x00, 0x18])
|
||||
//
|
||||
// _lenBE = UInt16(extData.count).bigEndian
|
||||
// withUnsafeBytes(of: &_lenBE) {
|
||||
// outData.append($0.baseAddress!.assumingMemoryBound(to: UInt8.self), count: $0.count)
|
||||
// }
|
||||
// outData.append(extData)
|
||||
//
|
||||
// var outputData = Data(capacity: outData.count + 9)
|
||||
// outputData.append(contentsOf: [0x16, 0x03, 0x01])
|
||||
// _lenBE = UInt16(outData.count + 4).bigEndian
|
||||
// withUnsafeBytes(of: &_lenBE) {
|
||||
// outputData.append($0.baseAddress!.assumingMemoryBound(to: UInt8.self), count: $0.count)
|
||||
// }
|
||||
// outputData.append(contentsOf: [0x01, 0x00])
|
||||
// _lenBE = UInt16(outData.count).bigEndian
|
||||
// withUnsafeBytes(of: &_lenBE) {
|
||||
// outputData.append($0.baseAddress!.assumingMemoryBound(to: UInt8.self), count: $0.count)
|
||||
// }
|
||||
// outputData.append(outData)
|
||||
// outputStreamProcessor.output(data: outputData)
|
||||
// }
|
||||
//
|
||||
// private func handleStatus1(data: Data) {
|
||||
// status = 8
|
||||
//
|
||||
// var outputData = Data()
|
||||
// outputData.append(contentsOf: [0x14, 0x03, 0x03, 0x00, 0x01, 0x01, 0x16, 0x03, 0x03, 0x00, 0x20])
|
||||
// var random = Data(count: 22)
|
||||
// Utils.Random.fill(data: &random)
|
||||
// outputData.append(random)
|
||||
//
|
||||
// var key = inputStreamProcessor.key
|
||||
// key.append(clientID)
|
||||
// outputData.withUnsafeBytes {
|
||||
// outputData.append(HMAC.final(value: $0.baseAddress!, length: outputData.count, algorithm: .SHA1, key: key).subdata(in: 0..<10))
|
||||
// }
|
||||
//
|
||||
// outputData.append(pack(data: data))
|
||||
//
|
||||
// outputStreamProcessor.output(data: outputData)
|
||||
// }
|
||||
//
|
||||
// private func handleInput(data: Data) throws {
|
||||
// buffer.append(data: data)
|
||||
// var unpackedData = Data()
|
||||
// while buffer.left > 5 {
|
||||
// buffer.skip(3)
|
||||
// var length: Int = 0
|
||||
// buffer.withUnsafeBytes { (ptr: UnsafePointer<UInt16>) in
|
||||
// length = Int(ptr.pointee.byteSwapped)
|
||||
// }
|
||||
// buffer.skip(2)
|
||||
// if buffer.left >= length {
|
||||
// unpackedData.append(buffer.get(length: length)!)
|
||||
// continue
|
||||
// } else {
|
||||
// buffer.setBack(length: 5)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// buffer.squeeze()
|
||||
// try inputStreamProcessor.input(data: unpackedData)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//}
|
||||
@@ -1,112 +0,0 @@
|
||||
//import Foundation
|
||||
//import CommonCrypto
|
||||
//
|
||||
///// This adapter connects to remote through Shadowsocks proxy.
|
||||
//public class ShadowsocksAdapter: AdapterSocket {
|
||||
// enum ShadowsocksAdapterStatus {
|
||||
// case invalid,
|
||||
// connecting,
|
||||
// connected,
|
||||
// forwarding,
|
||||
// stopped
|
||||
// }
|
||||
//
|
||||
// enum EncryptMethod: String {
|
||||
// case AES128CFB = "AES-128-CFB", AES192CFB = "AES-192-CFB", AES256CFB = "AES-256-CFB"
|
||||
//
|
||||
// static let allValues: [EncryptMethod] = [.AES128CFB, .AES192CFB, .AES256CFB]
|
||||
// }
|
||||
//
|
||||
// public let host: String
|
||||
// public let port: Int
|
||||
//
|
||||
// var internalStatus: ShadowsocksAdapterStatus = .invalid
|
||||
//
|
||||
// private let protocolObfuscater: ProtocolObfuscater.ProtocolObfuscaterBase
|
||||
// private let cryptor: CryptoStreamProcessor
|
||||
// private let streamObfuscator: StreamObfuscater.StreamObfuscaterBase
|
||||
//
|
||||
// public init(host: String, port: Int, protocolObfuscater: ProtocolObfuscater.ProtocolObfuscaterBase, cryptor: CryptoStreamProcessor, streamObfuscator: StreamObfuscater.StreamObfuscaterBase) {
|
||||
// self.host = host
|
||||
// self.port = port
|
||||
// self.protocolObfuscater = protocolObfuscater
|
||||
// self.cryptor = cryptor
|
||||
// self.streamObfuscator = streamObfuscator
|
||||
//
|
||||
// super.init()
|
||||
//
|
||||
// protocolObfuscater.inputStreamProcessor = cryptor
|
||||
// protocolObfuscater.outputStreamProcessor = self
|
||||
//
|
||||
// cryptor.inputStreamProcessor = streamObfuscator
|
||||
// cryptor.outputStreamProcessor = protocolObfuscater
|
||||
//
|
||||
// streamObfuscator.inputStreamProcessor = self
|
||||
// streamObfuscator.outputStreamProcessor = cryptor
|
||||
// }
|
||||
//
|
||||
// override public func openSocketWith(session: ConnectSession) {
|
||||
// super.openSocketWith(session: session)
|
||||
//
|
||||
// do {
|
||||
// internalStatus = .connecting
|
||||
// try socket.connectTo(host: host, port: port, enableTLS: false, tlsSettings: nil)
|
||||
// } catch let error {
|
||||
// observer?.signal(.errorOccured(error, on: self))
|
||||
// disconnect()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override public func didConnectWith(socket: RawTCPSocketProtocol) {
|
||||
// super.didConnectWith(socket: socket)
|
||||
//
|
||||
// internalStatus = .connected
|
||||
//
|
||||
// protocolObfuscater.start()
|
||||
// }
|
||||
//
|
||||
// override public func didRead(data: Data, from socket: RawTCPSocketProtocol) {
|
||||
// super.didRead(data: data, from: socket)
|
||||
//
|
||||
// do {
|
||||
// try protocolObfuscater.input(data: data)
|
||||
// } catch {
|
||||
// disconnect()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public override func write(data: Data) {
|
||||
// streamObfuscator.output(data: data)
|
||||
// }
|
||||
//
|
||||
// public func write(rawData: Data) {
|
||||
// super.write(data: rawData)
|
||||
// }
|
||||
//
|
||||
// public func input(data: Data) {
|
||||
// delegate?.didRead(data: data, from: self)
|
||||
// }
|
||||
//
|
||||
// public func output(data: Data) {
|
||||
// write(rawData: data)
|
||||
// }
|
||||
//
|
||||
// override public func didWrite(data: Data?, by socket: RawTCPSocketProtocol) {
|
||||
// super.didWrite(data: data, by: socket)
|
||||
//
|
||||
// protocolObfuscater.didWrite()
|
||||
//
|
||||
// switch internalStatus {
|
||||
// case .forwarding:
|
||||
// delegate?.didWrite(data: data, by: self)
|
||||
// default:
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func becomeReadyToForward() {
|
||||
// internalStatus = .forwarding
|
||||
// observer?.signal(.readyForForward(self))
|
||||
// delegate?.didBecomeReadyToForwardWith(socket: self)
|
||||
// }
|
||||
//}
|
||||
@@ -1,167 +0,0 @@
|
||||
//import Foundation
|
||||
//
|
||||
//extension ShadowsocksAdapter {
|
||||
// public struct StreamObfuscater {
|
||||
// public class Factory {
|
||||
// public init() {}
|
||||
//
|
||||
// public func build(for session: ConnectSession) -> StreamObfuscaterBase {
|
||||
// return StreamObfuscaterBase(for: session)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public class StreamObfuscaterBase {
|
||||
// public weak var inputStreamProcessor: ShadowsocksAdapter!
|
||||
// private weak var _outputStreamProcessor: CryptoStreamProcessor!
|
||||
// public var outputStreamProcessor: CryptoStreamProcessor! {
|
||||
// get {
|
||||
// return _outputStreamProcessor
|
||||
// }
|
||||
// set {
|
||||
// _outputStreamProcessor = newValue
|
||||
// key = _outputStreamProcessor?.key
|
||||
// writeIV = _outputStreamProcessor?.writeIV
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public var key: Data?
|
||||
// public var writeIV: Data?
|
||||
//
|
||||
// let session: ConnectSession
|
||||
//
|
||||
// init(for session: ConnectSession) {
|
||||
// self.session = session
|
||||
// }
|
||||
//
|
||||
// func output(data: Data) {}
|
||||
// func input(data: Data) throws {}
|
||||
// }
|
||||
//
|
||||
// public class OriginStreamObfuscater: StreamObfuscaterBase {
|
||||
// public class Factory: StreamObfuscater.Factory {
|
||||
// public override init() {}
|
||||
//
|
||||
// public override func build(for session: ConnectSession) -> ShadowsocksAdapter.StreamObfuscater.StreamObfuscaterBase {
|
||||
// return OriginStreamObfuscater(for: session)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private var requestSend = false
|
||||
//
|
||||
// private func requestData(withData data: Data) -> Data {
|
||||
// let hostLength = session.host.utf8.count
|
||||
// let length = 1 + 1 + hostLength + 2 + data.count
|
||||
// var response = Data(count: length)
|
||||
// response[0] = 3
|
||||
// response[1] = UInt8(hostLength)
|
||||
// response.replaceSubrange(2..<2+hostLength, with: session.host.utf8)
|
||||
// var beport = UInt16(session.port).bigEndian
|
||||
// withUnsafeBytes(of: &beport) {
|
||||
// response.replaceSubrange(2+hostLength..<4+hostLength, with: $0)
|
||||
// }
|
||||
// response.replaceSubrange(4+hostLength..<length, with: data)
|
||||
// return response
|
||||
// }
|
||||
//
|
||||
// public override func input(data: Data) throws {
|
||||
// inputStreamProcessor!.input(data: data)
|
||||
// }
|
||||
//
|
||||
// public override func output(data: Data) {
|
||||
// if requestSend {
|
||||
// return outputStreamProcessor!.output(data: data)
|
||||
// } else {
|
||||
// requestSend = true
|
||||
// return outputStreamProcessor!.output(data: requestData(withData: data))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public class OTAStreamObfuscater: StreamObfuscaterBase {
|
||||
// public class Factory: StreamObfuscater.Factory {
|
||||
// public override init() {}
|
||||
//
|
||||
// public override func build(for session: ConnectSession) -> ShadowsocksAdapter.StreamObfuscater.StreamObfuscaterBase {
|
||||
// return OTAStreamObfuscater(for: session)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private var count: UInt32 = 0
|
||||
//
|
||||
// private let DATA_BLOCK_SIZE = 0xFFFF - 12
|
||||
//
|
||||
// private var requestSend = false
|
||||
//
|
||||
// private func requestData() -> Data {
|
||||
// var response: [UInt8] = [0x13]
|
||||
// response.append(UInt8(session.host.utf8.count))
|
||||
// response += [UInt8](session.host.utf8)
|
||||
// response += [UInt8](Utils.toByteArray(UInt16(session.port)).reversed())
|
||||
// var responseData = Data(bytes: UnsafePointer<UInt8>(response), count: response.count)
|
||||
// var keyiv = Data(count: key!.count + writeIV!.count)
|
||||
//
|
||||
// keyiv.replaceSubrange(0..<writeIV!.count, with: writeIV!)
|
||||
// keyiv.replaceSubrange(writeIV!.count..<writeIV!.count + key!.count, with: key!)
|
||||
// responseData.append(HMAC.final(value: responseData, algorithm: .SHA1, key: keyiv).subdata(in: 0..<10))
|
||||
// return responseData
|
||||
// }
|
||||
//
|
||||
// public override func input(data: Data) throws {
|
||||
// inputStreamProcessor!.input(data: data)
|
||||
// }
|
||||
//
|
||||
// public override func output(data: Data) {
|
||||
// let fullBlockCount = data.count / DATA_BLOCK_SIZE
|
||||
// var outputSize = fullBlockCount * (DATA_BLOCK_SIZE + 10 + 2)
|
||||
// if data.count > fullBlockCount * DATA_BLOCK_SIZE {
|
||||
// outputSize += data.count - fullBlockCount * DATA_BLOCK_SIZE + 10 + 2
|
||||
// }
|
||||
//
|
||||
// let _requestData: Data = requestData()
|
||||
// if !requestSend {
|
||||
// outputSize += _requestData.count
|
||||
// }
|
||||
//
|
||||
// var outputData = Data(count: outputSize)
|
||||
// var outputOffset = 0
|
||||
// var dataOffset = 0
|
||||
//
|
||||
// if !requestSend {
|
||||
// requestSend = true
|
||||
// outputData.replaceSubrange(0..<_requestData.count, with: _requestData)
|
||||
// outputOffset += _requestData.count
|
||||
// }
|
||||
//
|
||||
// while outputOffset != outputSize {
|
||||
// let blockLength = min(data.count - dataOffset, DATA_BLOCK_SIZE)
|
||||
// var len = UInt16(blockLength).bigEndian
|
||||
// withUnsafeBytes(of: &len) {
|
||||
// outputData.replaceSubrange(outputOffset..<outputOffset+2, with: $0)
|
||||
// }
|
||||
//
|
||||
// var kc = Data(count: writeIV!.count + MemoryLayout.size(ofValue: count))
|
||||
// kc.replaceSubrange(0..<writeIV!.count, with: writeIV!)
|
||||
// var c = count.bigEndian
|
||||
// let ms = MemoryLayout.size(ofValue: c)
|
||||
// withUnsafeBytes(of: &c) {
|
||||
// kc.replaceSubrange(writeIV!.count..<writeIV!.count+ms, with: $0)
|
||||
// }
|
||||
//
|
||||
// data.withUnsafeBytes {
|
||||
// outputData.replaceSubrange(outputOffset+2..<outputOffset+12, with: HMAC.final(value: $0.baseAddress!.advanced(by: dataOffset), length: blockLength, algorithm: .SHA1, key: kc).subdata(in: 0..<10))
|
||||
// }
|
||||
//
|
||||
// data.withUnsafeBytes {
|
||||
// outputData.replaceSubrange(outputOffset+12..<outputOffset+12+blockLength, with: $0.baseAddress!.advanced(by: dataOffset), count: blockLength)
|
||||
// }
|
||||
//
|
||||
// count += 1
|
||||
// outputOffset += 12 + blockLength
|
||||
// dataOffset += blockLength
|
||||
// }
|
||||
//
|
||||
// return outputStreamProcessor!.output(data: outputData)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -27,7 +27,7 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="52.173913043478265" y="375"/>
|
||||
<point key="canvasLocation" x="120" y="100"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
||||
@@ -1,13 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="sfA-EG-18J">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" 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="15706"/>
|
||||
<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>
|
||||
<!--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="hm5-7q-Zfi" kind="relationship" relationship="viewControllers" id="pfK-BR-9lf"/>
|
||||
<segue destination="dIk-JY-9vE" kind="relationship" relationship="viewControllers" id="AwW-3j-iAg"/>
|
||||
</connections>
|
||||
</tabBarController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="RDz-8t-yhN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-819" 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="pdd-aM-sKl" kind="relationship" relationship="rootViewController" id="oMe-a0-xN7"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="8j4-AX-JBN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="0.0" y="-1250"/>
|
||||
</scene>
|
||||
<!--Domains-->
|
||||
<scene sceneID="MN1-aZ-cZt">
|
||||
<objects>
|
||||
@@ -17,11 +53,11 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="default" hidesAccessoryWhenEditing="NO" indentationWidth="10" reuseIdentifier="DomainCell" textLabel="0HB-5f-eB1" detailTextLabel="MRe-Eq-gvc" style="IBUITableViewCellStyleSubtitle" id="F8D-aK-j1W">
|
||||
<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="55.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="320" height="55.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="55.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="0HB-5f-eB1">
|
||||
@@ -41,7 +77,7 @@
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="WcC-nb-Vf5" kind="show" id="EVQ-hO-JE9"/>
|
||||
<segue destination="WcC-nb-Vf5" kind="push" id="EVQ-hO-JE9"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
@@ -54,7 +90,7 @@
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="jfx-iA-E0v" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="686" y="-1245"/>
|
||||
<point key="canvasLocation" x="700" y="-1250"/>
|
||||
</scene>
|
||||
<!--Hosts-->
|
||||
<scene sceneID="ZCV-Yx-jjW">
|
||||
@@ -65,11 +101,11 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="HostCell" textLabel="Rnk-SP-UHm" detailTextLabel="ovQ-lJ-hWJ" style="IBUITableViewCellStyleSubtitle" id="uv0-9B-Zbb">
|
||||
<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="28" width="320" height="55.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="320" height="55.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="55.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Rnk-SP-UHm">
|
||||
@@ -89,7 +125,7 @@
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="h7Z-Qr-pJ5" kind="show" id="TPa-Zn-eOs"/>
|
||||
<segue destination="h7Z-Qr-pJ5" kind="push" id="TPa-Zn-eOs"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
@@ -102,7 +138,7 @@
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Gdi-Xi-JUL" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1391" y="-1245"/>
|
||||
<point key="canvasLocation" x="1400" y="-1250"/>
|
||||
</scene>
|
||||
<!--Occurrences-->
|
||||
<scene sceneID="ws3-sK-l8m">
|
||||
@@ -140,94 +176,405 @@
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="UxH-PH-KQy" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2096" y="-1245"/>
|
||||
<point key="canvasLocation" x="2100" y="-1250"/>
|
||||
</scene>
|
||||
<!--Requests-->
|
||||
<scene sceneID="bDO-X1-bCe">
|
||||
<!--Recordings-->
|
||||
<scene sceneID="ODR-PD-nTU">
|
||||
<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">
|
||||
<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="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Wz5-zb-gwz">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="519"/>
|
||||
<subviews>
|
||||
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="La3-9e-6TK" userLabel="NewRec">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="90"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00.000" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="rbR-np-cXD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<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" id="vAq-EZ-Gmx">
|
||||
<rect key="frame" x="0.0" y="54" width="320" height="36"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<state key="normal" title="Stop Recording">
|
||||
<color key="titleColor" systemColor="systemRedColor" red="1" green="0.23137254900000001" blue="0.18823529410000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="startRecordingButtonTapped:" destination="hm5-7q-Zfi" eventType="touchUpInside" id="hEp-UI-i6R"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="90" id="bqy-bR-yVI"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="v3Z-HR-abM">
|
||||
<rect key="frame" x="0.0" y="98" width="320" height="421"/>
|
||||
<connections>
|
||||
<segue destination="C7Q-Vu-xAC" kind="embed" id="ZTW-t1-5G1"/>
|
||||
</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>
|
||||
<tabBarItem key="tabBarItem" title="Recordings" image="tag" id="mGk-aq-MRP"/>
|
||||
<connections>
|
||||
<outlet property="startButton" destination="vAq-EZ-Gmx" id="FSo-GH-jtd"/>
|
||||
<outlet property="startNewRecView" destination="La3-9e-6TK" id="I5w-aK-DlY"/>
|
||||
<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="-550"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="GQx-dK-qb5">
|
||||
<objects>
|
||||
<navigationController id="C7Q-Vu-xAC" sceneMemberID="viewController">
|
||||
<navigationItem key="navigationItem" id="mCN-Hk-Z5i"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translucent="NO" id="ByI-P4-oVv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<toolbar key="toolbar" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="NtQ-rp-G6c">
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</toolbar>
|
||||
<connections>
|
||||
<segue destination="Fln-DD-aId" kind="relationship" relationship="rootViewController" id="smF-1g-aDM"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="GEP-3e-6Ko" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="700" y="-550"/>
|
||||
</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="377"/>
|
||||
<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="55.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="55.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="hr0-Xt-5gV">
|
||||
<rect key="frame" x="16" y="10" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Xav-Ub-clj">
|
||||
<rect key="frame" x="16" y="31.5" width="44" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<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="1400" y="-550"/>
|
||||
</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="421"/>
|
||||
<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="Kff-ed-gdd"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" enabled="NO" style="done" systemItem="save" id="rWg-hE-Ydl">
|
||||
<connections>
|
||||
<action selector="didTapSave:" destination="VRk-wv-rhk" id="Xee-qo-bQx"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</items>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="klV-Ed-xzV" appends="YES" id="Huf-jb-4Ef"/>
|
||||
</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="56" width="288" height="355"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Guy-Ra-fpS" userLabel="Title">
|
||||
<rect key="frame" x="0.0" y="0.0" width="288" height="58"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Et0-8d-CId">
|
||||
<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>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Unnamed Recording #12345678" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" id="OCX-wu-l5d">
|
||||
<rect key="frame" x="4" y="24" width="280" height="34"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" returnKeyType="next"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="VRk-wv-rhk" id="uJL-hB-9w7"/>
|
||||
</connections>
|
||||
</textField>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="58" id="5ew-Cq-VKh"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ybL-UG-dwT" userLabel="Notes">
|
||||
<rect key="frame" x="0.0" y="66" width="288" height="190"/>
|
||||
<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="166"/>
|
||||
<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" type="system" pointSize="14"/>
|
||||
<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="264" 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" fixedFrame="YES" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" bouncesZoom="NO" editable="NO" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pql-H5-k6U">
|
||||
<rect key="frame" x="0.0" y="24" width="288" height="67"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<string key="text">Start: 1970-01-01 01:00
|
||||
End: 1970-01-01 02:00
|
||||
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>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" priority="250" constant="91" id="or7-9o-FZb"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="ybL-UG-dwT" firstAttribute="width" secondItem="Guy-Ra-fpS" secondAttribute="width" id="PUH-xO-ZbD"/>
|
||||
<constraint firstItem="QiY-Mm-Dej" firstAttribute="width" secondItem="Guy-Ra-fpS" 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="12" id="Lec-83-aaD"/>
|
||||
<constraint firstItem="xdn-EU-IMx" firstAttribute="trailing" secondItem="fMa-Lq-tGz" secondAttribute="trailing" constant="-16" id="hhC-bL-G3S"/>
|
||||
<constraint firstItem="xdn-EU-IMx" firstAttribute="bottom" secondItem="fMa-Lq-tGz" secondAttribute="bottom" constant="-10" id="p7W-sr-Wch"/>
|
||||
<constraint firstItem="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="buttonCancel" destination="TGg-60-wZW" id="5Ej-7t-jaD"/>
|
||||
<outlet property="buttonSave" destination="rWg-hE-Ydl" id="zfM-kx-erX"/>
|
||||
<outlet property="inputDetails" destination="pql-H5-k6U" id="NXm-8f-5E6"/>
|
||||
<outlet property="inputNotes" destination="NXU-yU-eST" id="c2n-cG-aLq"/>
|
||||
<outlet property="inputTitle" destination="OCX-wu-l5d" id="PeC-F5-4mx"/>
|
||||
<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="klV-Ed-xzV">
|
||||
<connections>
|
||||
<action selector="hideKeyboard" destination="VRk-wv-rhk" id="iDb-kK-nli"/>
|
||||
</connections>
|
||||
</tapGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2100" y="-550"/>
|
||||
</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" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="cLV-Db-JxM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="377"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="PreviousRecordDetailCell" textLabel="rN0-kA-Eln" detailTextLabel="xRp-XG-oKf" style="IBUITableViewCellStyleValue1" id="ceT-cF-lLF">
|
||||
<rect key="frame" x="0.0" y="28" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ceT-cF-lLF" id="c5Y-xg-hSL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="rN0-kA-Eln">
|
||||
<rect key="frame" x="16" y="12" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<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" type="system" pointSize="17"/>
|
||||
<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"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="lan-I9-b0a" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2800" y="-550"/>
|
||||
</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="pdd-aM-sKl" kind="relationship" relationship="rootViewController" id="oMe-a0-xN7"/>
|
||||
<segue destination="qdB-ZO-LHY" kind="relationship" relationship="rootViewController" id="qJW-Jc-O4D"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="8j4-AX-JBN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bg9-bR-vlx" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-21" y="-1245"/>
|
||||
<point key="canvasLocation" x="0.0" y="150"/>
|
||||
</scene>
|
||||
<!--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" bounces="NO" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="8kq-PY-wp7">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" bounces="NO" 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="General Settings" id="w58-6X-Jea">
|
||||
<tableViewSection headerTitle="VPN Proxy Settings" id="w58-6X-Jea">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="ghM-ze-fvp">
|
||||
<rect key="frame" x="0.0" y="55.5" width="320" height="43.5"/>
|
||||
<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="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kmY-ot-lJW">
|
||||
<rect key="frame" x="256" y="6" width="45" height="31"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kmY-ot-lJW">
|
||||
<rect key="frame" x="255" y="6.5" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleVPNProxy:" destination="qdB-ZO-LHY" eventType="valueChanged" id="y95-2Z-Uep"/>
|
||||
</connections>
|
||||
</switch>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="VPN Proxy enabled" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Qha-4I-go0">
|
||||
<rect key="frame" x="16" y="5" width="230" height="27"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="VPN Proxy enabled" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qha-4I-go0">
|
||||
<rect key="frame" x="16" y="12" width="147" height="20"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Qyy-0U-yhd">
|
||||
<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="Qyy-0U-yhd" id="Mfs-fu-W5k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="9Ko-sD-7x0">
|
||||
<rect key="frame" x="95" y="7" width="124" height="30"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<state key="normal" title="Export DB"/>
|
||||
<connections>
|
||||
<action selector="exportDB:" destination="qdB-ZO-LHY" eventType="touchUpInside" id="3gu-WF-3Xa"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="wzU-8s-HGb">
|
||||
<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="wzU-8s-HGb" id="aNM-6U-bho">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="S6B-i8-CoC">
|
||||
<rect key="frame" x="94" y="7" width="125" height="30"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<state key="normal" title="Delete all logs"/>
|
||||
<connections>
|
||||
<action selector="clearDatabaseResults:" destination="qdB-ZO-LHY" eventType="touchUpInside" id="w0d-8F-GmN"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="kmY-ot-lJW" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Qha-4I-go0" secondAttribute="trailing" constant="8" id="Lnx-hC-xOx"/>
|
||||
<constraint firstItem="kmY-ot-lJW" firstAttribute="trailing" secondItem="d2v-vz-QIB" secondAttribute="trailingMargin" id="Ylz-D4-hz4"/>
|
||||
<constraint firstItem="Qha-4I-go0" firstAttribute="centerY" secondItem="d2v-vz-QIB" secondAttribute="centerY" id="dKE-By-qEu"/>
|
||||
<constraint firstItem="kmY-ot-lJW" firstAttribute="centerY" secondItem="Qha-4I-go0" secondAttribute="centerY" id="dgh-tx-Y8a"/>
|
||||
<constraint firstItem="Qha-4I-go0" firstAttribute="leading" secondItem="d2v-vz-QIB" secondAttribute="leadingMargin" id="rHx-0D-DPX"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
@@ -235,10 +582,10 @@
|
||||
<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="242" width="320" height="43.5"/>
|
||||
<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="43.5"/>
|
||||
<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">
|
||||
@@ -258,14 +605,14 @@
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="q3B-Yi-1bx" kind="show" identifier="segueFilterIgnored" id="EzT-Xq-wka"/>
|
||||
<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="285.5" width="320" height="43.5"/>
|
||||
<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="43.5"/>
|
||||
<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">
|
||||
@@ -285,11 +632,80 @@
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="q3B-Yi-1bx" kind="show" identifier="segueFilterBlocked" id="cOY-j0-75m"/>
|
||||
<segue destination="q3B-Yi-1bx" kind="push" identifier="segueFilterBlocked" id="cOY-j0-75m"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Other Settings" id="wLR-T2-Qxm">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Qyy-0U-yhd">
|
||||
<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" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qyy-0U-yhd" id="Mfs-fu-W5k">
|
||||
<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="9Ko-sD-7x0">
|
||||
<rect key="frame" x="125" y="7" width="70" height="30"/>
|
||||
<state key="normal" title="Export DB"/>
|
||||
<connections>
|
||||
<action selector="exportDB:" destination="qdB-ZO-LHY" eventType="touchUpInside" id="3gu-WF-3Xa"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="9Ko-sD-7x0" firstAttribute="centerX" secondItem="Mfs-fu-W5k" secondAttribute="centerX" id="LzG-xg-XTg"/>
|
||||
<constraint firstItem="9Ko-sD-7x0" firstAttribute="centerY" secondItem="Mfs-fu-W5k" secondAttribute="centerY" id="SXw-dC-2kl"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="wzU-8s-HGb">
|
||||
<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" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="wzU-8s-HGb" id="aNM-6U-bho">
|
||||
<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="S6B-i8-CoC">
|
||||
<rect key="frame" x="74.5" y="7" width="171" height="30"/>
|
||||
<state key="normal" title="Reset Introduction Alerts"/>
|
||||
<connections>
|
||||
<action selector="resetTutorialAlerts:" destination="qdB-ZO-LHY" eventType="touchUpInside" id="0GX-Ko-bk2"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="S6B-i8-CoC" firstAttribute="centerY" secondItem="aNM-6U-bho" secondAttribute="centerY" id="Wet-iT-mke"/>
|
||||
<constraint firstItem="S6B-i8-CoC" firstAttribute="centerX" secondItem="aNM-6U-bho" secondAttribute="centerX" id="qM6-0t-1m4"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="a9C-Qy-pOf">
|
||||
<rect key="frame" x="0.0" y="387.5" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="a9C-Qy-pOf" id="cUk-4x-Weg">
|
||||
<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="17e-nR-aCh">
|
||||
<rect key="frame" x="111.5" y="7" width="97" height="30"/>
|
||||
<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="Rep-Do-4OQ"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="17e-nR-aCh" firstAttribute="centerX" secondItem="cUk-4x-Weg" secondAttribute="centerX" id="dU5-1x-ETF"/>
|
||||
<constraint firstItem="17e-nR-aCh" firstAttribute="centerY" secondItem="cUk-4x-Weg" secondAttribute="centerY" id="nLq-yi-u2E"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="qdB-ZO-LHY" id="RH3-xR-dpC"/>
|
||||
@@ -305,13 +721,13 @@
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="VNK-Z0-T0a" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="684" y="127"/>
|
||||
<point key="canvasLocation" x="700" y="150"/>
|
||||
</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">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="GSg-ZZ-F8J">
|
||||
<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"/>
|
||||
@@ -339,102 +755,25 @@
|
||||
<outlet property="delegate" destination="q3B-Yi-1bx" id="02X-f0-d1a"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Domains" id="FWA-IG-VIb"/>
|
||||
<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="1389" y="127"/>
|
||||
</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="qdB-ZO-LHY" kind="relationship" relationship="rootViewController" id="qJW-Jc-O4D"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bg9-bR-vlx" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-23" y="127"/>
|
||||
</scene>
|
||||
<!--Recordings-->
|
||||
<scene sceneID="ODR-PD-nTU">
|
||||
<objects>
|
||||
<viewController id="hm5-7q-Zfi" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="JYr-yE-eGS">
|
||||
<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"/>
|
||||
<viewLayoutGuide key="safeArea" id="Jq8-ke-k0B"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" title="Recordings" image="tag" id="mGk-aq-MRP"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Wfy-Tp-A9o" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-21" y="-560"/>
|
||||
</scene>
|
||||
<!--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="cGm-zQ-NnO" kind="presentation" identifier="welcome" id="aF0-OB-Mwx"/>
|
||||
<segue destination="RcB-4v-fd4" kind="relationship" relationship="viewControllers" id="cmC-pu-5n2"/>
|
||||
<segue destination="hm5-7q-Zfi" kind="relationship" relationship="viewControllers" id="pfK-BR-9lf"/>
|
||||
<segue destination="dIk-JY-9vE" kind="relationship" relationship="viewControllers" id="AwW-3j-iAg"/>
|
||||
</connections>
|
||||
</tabBarController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="RDz-8t-yhN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-831" y="127"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="8iq-nV-o0O">
|
||||
<objects>
|
||||
<viewController id="cGm-zQ-NnO" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="FlS-lu-XEg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="548"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" editable="NO" selectable="NO" id="QWn-iX-27k">
|
||||
<rect key="frame" x="16" y="20" width="288" height="508"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<accessibility key="accessibilityConfiguration">
|
||||
<accessibilityTraits key="traits" staticText="YES" notEnabled="YES"/>
|
||||
<bool key="isElement" value="YES"/>
|
||||
</accessibility>
|
||||
<string key="text">Your data belongs to you. Therefore, monitoring and analysis take place on your device only. The app does not share any data with us or any other third-party.</string>
|
||||
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="SJX-Gb-WTN"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="nve-Iu-WIa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-831" y="841"/>
|
||||
<point key="canvasLocation" x="1400" y="150"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="EzT-Xq-wka"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
<resources>
|
||||
<image name="journal" width="25" height="25"/>
|
||||
<image name="settings" width="25" height="25"/>
|
||||
<image name="tag" width="25" height="25"/>
|
||||
</resources>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="cOY-j0-75m"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
|
||||
@@ -19,7 +19,9 @@ extension EditableRows where Self: UITableViewController {
|
||||
let userInfo = editableRowUserInfo(index)
|
||||
return editableRowActions(index).compactMap { a,t in
|
||||
let x = UITableViewRowAction(style: a == .delete ? .destructive : .normal, title: t) { self.editableRowCallback($1, a, userInfo) }
|
||||
x.backgroundColor = editableRowActionColor(index, a)
|
||||
if let color = editableRowActionColor(index, a) {
|
||||
x.backgroundColor = color
|
||||
}
|
||||
return x
|
||||
}
|
||||
}
|
||||
@@ -123,3 +125,23 @@ extension TVCFilter : EditableRows {
|
||||
getRowActionsIOS11(indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
extension TVCPreviousRecords : EditableRows {
|
||||
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
|
||||
getRowActionsIOS9(indexPath)
|
||||
}
|
||||
@available(iOS 11.0, *)
|
||||
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
getRowActionsIOS11(indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
extension TVCRecordingDetails : EditableRows {
|
||||
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
|
||||
getRowActionsIOS9(indexPath)
|
||||
}
|
||||
@available(iOS 11.0, *)
|
||||
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
getRowActionsIOS11(indexPath)
|
||||
}
|
||||
}
|
||||
70
main/Common Classes/QuickUI.swift
Normal file
70
main/Common Classes/QuickUI.swift
Normal file
@@ -0,0 +1,70 @@
|
||||
import UIKit
|
||||
|
||||
struct QuickUI {
|
||||
|
||||
static func button(_ title: String, target: Any? = nil, action: Selector? = nil) -> UIButton {
|
||||
let x = UIButton(type: .roundedRect)
|
||||
x.setTitle(title, for: .normal)
|
||||
x.titleLabel?.font = .preferredFont(forTextStyle: .body)
|
||||
x.sizeToFit()
|
||||
if let a = action { x.addTarget(target, action: a, for: .touchUpInside) }
|
||||
if #available(iOS 10.0, *) {
|
||||
x.titleLabel?.adjustsFontForContentSizeCategory = true
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
static func image(_ img: UIImage?, frame: CGRect = CGRect.zero) -> UIImageView {
|
||||
let x = UIImageView(frame: frame)
|
||||
x.contentMode = .scaleAspectFit
|
||||
x.image = img
|
||||
return x
|
||||
}
|
||||
|
||||
static func text(_ str: String, frame: CGRect = CGRect.zero) -> UITextView {
|
||||
let x = UITextView(frame: frame)
|
||||
x.font = .preferredFont(forTextStyle: .body) // .systemFont(ofSize: UIFont.systemFontSize)
|
||||
x.isSelectable = false
|
||||
x.isEditable = false
|
||||
x.text = str
|
||||
if #available(iOS 10.0, *) {
|
||||
x.adjustsFontForContentSizeCategory = true
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
static func text(attributed: NSAttributedString, frame: CGRect = CGRect.zero) -> UITextView {
|
||||
let txt = self.text("", frame: frame)
|
||||
txt.attributedText = attributed
|
||||
return txt
|
||||
}
|
||||
}
|
||||
|
||||
extension NSMutableAttributedString {
|
||||
static private var def: UIFont = .preferredFont(forTextStyle: .body)
|
||||
|
||||
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) }
|
||||
|
||||
private func append(_ str: String, withFont: UIFont) -> Self {
|
||||
append(NSAttributedString(string: str, attributes: [
|
||||
.font : withFont,
|
||||
.foregroundColor : UIColor.sysFg
|
||||
]))
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension UIFont {
|
||||
func withTraits(traits: UIFontDescriptor.SymbolicTraits) -> UIFont {
|
||||
UIFont(descriptor: fontDescriptor.withSymbolicTraits(traits)!, size: 0) // keep size as is
|
||||
}
|
||||
func bold() -> UIFont { withTraits(traits: .traitBold) }
|
||||
func italic() -> UIFont { withTraits(traits: .traitItalic) }
|
||||
}
|
||||
199
main/Common Classes/TutorialSheet.swift
Normal file
199
main/Common Classes/TutorialSheet.swift
Normal file
@@ -0,0 +1,199 @@
|
||||
import UIKit
|
||||
|
||||
fileprivate let margin: CGFloat = 20
|
||||
fileprivate let cornerRadius: CGFloat = 15
|
||||
fileprivate let uniRect = CGRect(x: 0, y: 0, width: 500, height: 500)
|
||||
|
||||
class TutorialSheet: UIViewController, UIScrollViewDelegate {
|
||||
|
||||
public var buttonTitleNext: String = "Next"
|
||||
public var buttonTitleDone: String = "Close"
|
||||
|
||||
private var priorIndex: Int?
|
||||
private var lastAnchor: NSLayoutConstraint?
|
||||
private var shouldAnimate: Bool = true
|
||||
private var shouldCloseBlock: (() -> Bool)? = nil
|
||||
private var didCloseBlock: (() -> Void)? = nil
|
||||
|
||||
private let sheetBg: UIView = {
|
||||
let x = UIView(frame: uniRect)
|
||||
x.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
x.backgroundColor = .sysBg
|
||||
x.layer.cornerRadius = cornerRadius
|
||||
x.layer.shadowColor = UIColor.black.cgColor
|
||||
x.layer.shadowRadius = 10
|
||||
x.layer.shadowOpacity = 0.75
|
||||
x.layer.shadowOffset = CGSize(width: 0, height: 4)
|
||||
return x
|
||||
}()
|
||||
|
||||
private let pager: UIPageControl = {
|
||||
let x = UIPageControl(frame: uniRect)
|
||||
x.frame.size.height = x.size(forNumberOfPages: 1).height
|
||||
x.currentPageIndicatorTintColor = UIColor.sysFg.withAlphaComponent(0.5)
|
||||
x.pageIndicatorTintColor = UIColor.sysFg.withAlphaComponent(0.25)
|
||||
x.numberOfPages = 0
|
||||
x.hidesForSinglePage = true
|
||||
x.addTarget(self, action: #selector(pagerDidChange), for: .valueChanged)
|
||||
return x
|
||||
}()
|
||||
|
||||
private let pageScroll: UIScrollView = {
|
||||
let x = UIScrollView(frame: uniRect)
|
||||
x.bounces = false
|
||||
x.isPagingEnabled = true
|
||||
x.showsVerticalScrollIndicator = false
|
||||
x.showsHorizontalScrollIndicator = false
|
||||
|
||||
let content = UIView()
|
||||
x.addSubview(content)
|
||||
content.translatesAutoresizingMaskIntoConstraints = false
|
||||
content.anchor([.left, .right, .top, .bottom], to: x)
|
||||
content.anchor([.width, .height], to: x) | .defaultLow
|
||||
return x
|
||||
}()
|
||||
|
||||
private let button: UIButton = {
|
||||
let x = QuickUI.button("", target: self, action: #selector(buttonTapped))
|
||||
x.contentEdgeInsets = UIEdgeInsets(all: 8)
|
||||
return x
|
||||
}()
|
||||
|
||||
|
||||
// MARK: Init
|
||||
|
||||
required init?(coder: NSCoder) { super.init(coder: coder) }
|
||||
|
||||
required init() {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
view = makeControlUI()
|
||||
modalPresentationStyle = .custom
|
||||
if #available(iOS 13.0, *) {
|
||||
isModalInPresentation = true
|
||||
}
|
||||
UIDevice.orientationDidChangeNotification.observe(call: #selector(didChangeOrientation), on: self)
|
||||
}
|
||||
|
||||
/// Present Tutorial Sheet Controller
|
||||
/// - Parameter viewController: If set to `nil`, use main application as canvas. (Default: `nil`)
|
||||
/// - Parameter animate: Use `present` and `dismiss` animations. (Default: `true`)
|
||||
/// - Parameter shouldClose: Called before the view controller is dismissed. Return `false` to prevent the dismissal.
|
||||
/// Use this block to extract user data from input fields. (Default: `nil`)
|
||||
/// - Parameter didClose: Called after the view controller is completely dismissed (with animations).
|
||||
/// Use this block to update UI and visible changes. (Default: `nil`)
|
||||
func present(in viewController: UIViewController? = nil, animate: Bool = true, shouldClose: (() -> Bool)? = nil, didClose: (() -> Void)? = nil) {
|
||||
guard let vc = viewController ?? UIApplication.shared.keyWindow?.rootViewController else {
|
||||
return
|
||||
}
|
||||
shouldCloseBlock = shouldClose
|
||||
didCloseBlock = didClose
|
||||
shouldAnimate = animate
|
||||
vc.present(self, animated: animate)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Dynamic UI
|
||||
|
||||
@discardableResult func addSheet(_ closure: ((UIStackView) -> Void)? = nil) -> UIStackView {
|
||||
pager.numberOfPages += 1
|
||||
updateButtonTitle()
|
||||
let x = UIStackView(frame: pageScroll.bounds)
|
||||
x.translatesAutoresizingMaskIntoConstraints = false
|
||||
x.axis = .vertical
|
||||
x.backgroundColor = UIColor.black
|
||||
x.isOpaque = true
|
||||
guard let content = pageScroll.subviews.first else {
|
||||
return x
|
||||
}
|
||||
let prev = content.subviews.last
|
||||
content.addSubview(x)
|
||||
x.anchor([.top, .width, .height], to: pageScroll)
|
||||
x.leadingAnchor =&= (prev==nil ? content.leadingAnchor : prev!.trailingAnchor)
|
||||
lastAnchor?.isActive = false
|
||||
lastAnchor = (x.trailingAnchor =&= pageScroll.trailingAnchor)
|
||||
closure?(x)
|
||||
return x
|
||||
}
|
||||
|
||||
|
||||
// MARK: Static UI
|
||||
|
||||
private func makeControlUI() -> UIView {
|
||||
pageScroll.delegate = self
|
||||
|
||||
sheetBg.addSubview(pager)
|
||||
sheetBg.addSubview(pageScroll)
|
||||
sheetBg.addSubview(button)
|
||||
|
||||
for x in sheetBg.subviews { x.translatesAutoresizingMaskIntoConstraints = false }
|
||||
|
||||
pager.anchor([.top, .left, .right], to: sheetBg)
|
||||
pageScroll.topAnchor =&= pager.bottomAnchor
|
||||
pageScroll.anchor([.left, .right, .top, .bottom], to: sheetBg, margin: cornerRadius/2) | .defaultHigh
|
||||
button.topAnchor =&= pageScroll.bottomAnchor
|
||||
button.anchor([.bottom, .centerX], to: sheetBg)
|
||||
// button.bottomAnchor =&= sheetBg.bottomAnchor - 30
|
||||
// button.centerXAnchor =&= sheetBg.centerXAnchor
|
||||
|
||||
let bg = UIView(frame: uniRect)
|
||||
bg.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
bg.addSubview(sheetBg)
|
||||
|
||||
let h: CGFloat = UIApplication.shared.isStatusBarHidden ? 0 : UIApplication.shared.statusBarFrame.height
|
||||
sheetBg.frame = bg.frame.inset(by: UIEdgeInsets(all: margin, top: margin + h))
|
||||
return bg
|
||||
}
|
||||
|
||||
|
||||
// MARK: Delegates
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
priorIndex = pager.currentPage
|
||||
}
|
||||
|
||||
@objc private func didChangeOrientation() {
|
||||
if let i = priorIndex {
|
||||
priorIndex = nil
|
||||
switchToSheet(i, animated: false)
|
||||
}
|
||||
for case let x as UIStackView in pageScroll.subviews.first!.subviews {
|
||||
x.axis = (x.frame.width > x.frame.height) ? .horizontal : .vertical
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
let w = scrollView.frame.width
|
||||
let new = Int((scrollView.contentOffset.x + w/2) / w)
|
||||
if pager.currentPage != new {
|
||||
pager.currentPage = new
|
||||
updateButtonTitle()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func pagerDidChange(sender: UIPageControl) {
|
||||
switchToSheet(sender.currentPage, animated: true)
|
||||
}
|
||||
|
||||
private func switchToSheet(_ i: Int, animated: Bool) {
|
||||
pageScroll.setContentOffset(CGPoint(x: CGFloat(i) * pageScroll.bounds.width, y: 0), animated: animated)
|
||||
}
|
||||
|
||||
private func updateButtonTitle() {
|
||||
let last = (pager.currentPage == pager.numberOfPages - 1)
|
||||
let title = last ? buttonTitleDone : buttonTitleNext
|
||||
if button.title(for: .normal) != title {
|
||||
button.setTitle(title, for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func buttonTapped() {
|
||||
let next = pager.currentPage + 1
|
||||
if next < pager.numberOfPages {
|
||||
switchToSheet(next, animated: true)
|
||||
} else {
|
||||
if shouldCloseBlock?() ?? true {
|
||||
dismiss(animated: shouldAnimate, completion: didCloseBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import UIKit
|
||||
|
||||
let DBWrp = DBWrapper()
|
||||
fileprivate var AppDB: SQLiteDatabase? { get { try? SQLiteDatabase.open() } }
|
||||
|
||||
class DBWrapper {
|
||||
private var latestModification: Timestamp = 0
|
||||
@@ -29,7 +30,7 @@ class DBWrapper {
|
||||
}
|
||||
|
||||
func dataF_list(_ filter: FilterOptions) -> [String] {
|
||||
Q.sync() { dataF.compactMap { $1.contains(filter) ? $0 : nil } }
|
||||
Q.sync() { dataF.compactMap { $1.contains(filter) ? $0 : nil } }.sorted()
|
||||
}
|
||||
|
||||
func dataF_counts() -> (blocked: Int, ignored: Int) {
|
||||
@@ -47,13 +48,14 @@ class DBWrapper {
|
||||
// MARK: - Init
|
||||
|
||||
func initContentOfDB() {
|
||||
QLog.Debug("SQLite path: \(URL.internalDB())")
|
||||
DispatchQueue.global().async {
|
||||
#if IOS_SIMULATOR
|
||||
// self.generateTestData()
|
||||
// DispatchQueue.main.async {
|
||||
// // dont know why main queue is needed, wont start otherwise
|
||||
// Timer.repeating(2, call: #selector(self.insertRandomEntry), on: self)
|
||||
// }
|
||||
self.generateTestData()
|
||||
DispatchQueue.main.async {
|
||||
// dont know why main queue is needed, wont start otherwise
|
||||
Timer.repeating(2, call: #selector(self.insertRandomEntry), on: self)
|
||||
}
|
||||
#endif
|
||||
self.dataF_init()
|
||||
self.dataAB_init()
|
||||
@@ -100,7 +102,7 @@ class DBWrapper {
|
||||
// MARK: - Partial Update History
|
||||
|
||||
@objc private func syncNewestLogs() {
|
||||
QLog.Debug("\(#function)")
|
||||
//QLog.Debug("\(#function)")
|
||||
#if !IOS_SIMULATOR
|
||||
guard currentVPNState == .on else { return }
|
||||
#endif
|
||||
@@ -220,6 +222,32 @@ class DBWrapper {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Recordings
|
||||
|
||||
func listOfRecordings() -> [Recording] { AppDB?.allRecordings() ?? [] }
|
||||
func recordingGetCurrent() -> Recording? { AppDB?.ongoingRecording() }
|
||||
func recordingStartNew() -> Recording? { try? AppDB?.startNewRecording() }
|
||||
|
||||
func recordingStop(_ r: inout Recording) { AppDB?.stopRecording(&r) }
|
||||
func recordingPersist(_ r: Recording) { AppDB?.persistRecordingLogs(r) }
|
||||
func recordingDetails(_ r: Recording) -> [RecordLog] { AppDB?.getRecordingsLogs(r) ?? [] }
|
||||
|
||||
func recordingUpdate(_ r: Recording) {
|
||||
AppDB?.updateRecording(r)
|
||||
NotifyRecordingChanged.post((r, false))
|
||||
}
|
||||
|
||||
func recordingDelete(_ r: Recording) {
|
||||
if (try? AppDB?.deleteRecording(r)) == true {
|
||||
NotifyRecordingChanged.post((r, true))
|
||||
}
|
||||
}
|
||||
|
||||
func recordingDeleteDetails(_ r: Recording, domain: String?) -> Bool {
|
||||
((try? AppDB?.deleteRecordingLogs(r.id, matchingDomain: domain)) ?? 0) > 0
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Helper methods
|
||||
|
||||
private func dataA_index(of domain: String) -> Int? {
|
||||
@@ -270,7 +298,7 @@ extension DBWrapper {
|
||||
}
|
||||
|
||||
@objc private func insertRandomEntry() {
|
||||
QLog.Debug("Inserting 1 periodic log entry")
|
||||
//QLog.Debug("Inserting 1 periodic log entry")
|
||||
try? AppDB?.insertDNSQuery("\(arc4random() % 5).count.test.com", blocked: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ struct GroupedDomain {
|
||||
|
||||
struct FilterOptions: OptionSet {
|
||||
let rawValue: Int32
|
||||
static let none = FilterOptions(rawValue: 0)
|
||||
static let none = FilterOptions([])
|
||||
static let blocked = FilterOptions(rawValue: 1 << 0)
|
||||
static let ignored = FilterOptions(rawValue: 1 << 1)
|
||||
static let any = FilterOptions(rawValue: 0b11)
|
||||
@@ -25,12 +25,9 @@ enum SQLiteError: Error {
|
||||
|
||||
// MARK: - SQLiteDatabase
|
||||
|
||||
var AppDB: SQLiteDatabase? { get { try? SQLiteDatabase.open() } }
|
||||
|
||||
class SQLiteDatabase {
|
||||
private let dbPointer: OpaquePointer?
|
||||
private init(dbPointer: OpaquePointer?) {
|
||||
// print("SQLite path: \(basePath!.absoluteString)")
|
||||
self.dbPointer = dbPointer
|
||||
}
|
||||
|
||||
@@ -133,18 +130,15 @@ private extension SQLiteDatabase {
|
||||
sqlite3_bind_text(stmt, col, (value as NSString).utf8String, -1, nil) == SQLITE_OK
|
||||
}
|
||||
|
||||
func bindTextOrNil(_ stmt: OpaquePointer, _ col: Int32, _ value: String?) -> Bool {
|
||||
sqlite3_bind_text(stmt, col, (value == nil) ? nil : (value! as NSString).utf8String, -1, nil) == SQLITE_OK
|
||||
}
|
||||
|
||||
func readText(_ stmt: OpaquePointer, _ col: Int32) -> String? {
|
||||
let val = sqlite3_column_text(stmt, col)
|
||||
return (val != nil ? String(cString: val!) : nil)
|
||||
}
|
||||
|
||||
func readGroupedDomain(_ stmt: OpaquePointer) -> GroupedDomain {
|
||||
GroupedDomain(domain: readText(stmt, 0) ?? "",
|
||||
total: sqlite3_column_int(stmt, 1),
|
||||
blocked: sqlite3_column_int(stmt, 2),
|
||||
lastModified: sqlite3_column_int64(stmt, 3))
|
||||
}
|
||||
|
||||
func allRows<T>(_ stmt: OpaquePointer, _ fn: (OpaquePointer) -> T) -> [T] {
|
||||
var r: [T] = []
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) { r.append(fn(stmt)) }
|
||||
@@ -162,6 +156,8 @@ extension SQLiteDatabase {
|
||||
func initScheme() {
|
||||
try? self.createTable(table: DNSQueryT.self)
|
||||
try? self.createTable(table: DNSFilterT.self)
|
||||
try? self.createTable(table: Recording.self)
|
||||
try? self.createTable(table: RecordingLog.self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,9 +172,9 @@ private struct DNSQueryT: SQLTable {
|
||||
static var createStatement: String {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS req(
|
||||
ts BIGINT DEFAULT (strftime('%s','now')),
|
||||
domain VARCHAR(255) NOT NULL,
|
||||
logOpt INT DEFAULT 0
|
||||
ts INTEGER DEFAULT (strftime('%s','now')),
|
||||
domain TEXT NOT NULL,
|
||||
logOpt INTEGER DEFAULT 0
|
||||
);
|
||||
"""
|
||||
}
|
||||
@@ -217,6 +213,13 @@ extension SQLiteDatabase {
|
||||
|
||||
// MARK: read
|
||||
|
||||
func readGroupedDomain(_ stmt: OpaquePointer) -> GroupedDomain {
|
||||
GroupedDomain(domain: readText(stmt, 0) ?? "",
|
||||
total: sqlite3_column_int(stmt, 1),
|
||||
blocked: sqlite3_column_int(stmt, 2),
|
||||
lastModified: sqlite3_column_int64(stmt, 3))
|
||||
}
|
||||
|
||||
func domainList(since ts: Timestamp = 0) -> [GroupedDomain]? {
|
||||
try? run(sql: "SELECT domain, COUNT(*), SUM(logOpt&1), MAX(ts) FROM req \(ts == 0 ? "" : "WHERE ts > ?") GROUP BY domain ORDER BY 4 DESC;", bind: {
|
||||
ts == 0 || self.bindInt64($0, 1, ts)
|
||||
@@ -251,8 +254,8 @@ private struct DNSFilterT: SQLTable {
|
||||
static var createStatement: String {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS filter(
|
||||
domain VARCHAR(255) UNIQUE NOT NULL,
|
||||
opt INT DEFAULT 0
|
||||
domain TEXT UNIQUE NOT NULL,
|
||||
opt INTEGER DEFAULT 0
|
||||
);
|
||||
"""
|
||||
}
|
||||
@@ -263,7 +266,7 @@ extension SQLiteDatabase {
|
||||
// MARK: read
|
||||
|
||||
func loadFilters() -> [String : FilterOptions]? {
|
||||
try? run(sql: "SELECT domain, opt FROM filter ORDER BY domain ASC;", bind: nil) {
|
||||
try? run(sql: "SELECT domain, opt FROM filter;", bind: nil) {
|
||||
allRowsKeyed($0) {
|
||||
(key: readText($0, 0) ?? "",
|
||||
value: FilterOptions(rawValue: sqlite3_column_int($0, 1)))
|
||||
@@ -302,3 +305,160 @@ extension SQLiteDatabase {
|
||||
do { try createFilter() } catch { updateFilter() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Recordings
|
||||
|
||||
struct Recording: SQLTable {
|
||||
let id: sqlite3_int64
|
||||
let start: Timestamp
|
||||
let stop: Timestamp?
|
||||
var appId: String? = nil
|
||||
var title: String? = nil
|
||||
var notes: String? = nil
|
||||
static var createStatement: String {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS rec(
|
||||
id INTEGER PRIMARY KEY,
|
||||
start INTEGER DEFAULT (strftime('%s','now')),
|
||||
stop INTEGER,
|
||||
appid TEXT,
|
||||
title TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
extension SQLiteDatabase {
|
||||
|
||||
// MARK: write
|
||||
|
||||
func startNewRecording() throws -> Recording {
|
||||
try run(sql: "INSERT INTO rec (stop) VALUES (NULL);", bind: nil) { stmt -> Recording in
|
||||
try ifStep(stmt, SQLITE_DONE)
|
||||
return try getRecording(withID: sqlite3_last_insert_rowid(dbPointer))
|
||||
}
|
||||
}
|
||||
|
||||
func stopRecording(_ r: inout Recording) {
|
||||
guard r.stop == nil else { return }
|
||||
let theID = r.id
|
||||
try? run(sql: "UPDATE rec SET stop = (strftime('%s','now')) WHERE id = ? LIMIT 1;", bind: {
|
||||
self.bindInt64($0, 1, theID)
|
||||
}) { stmt -> Void in
|
||||
try ifStep(stmt, SQLITE_DONE)
|
||||
r = try getRecording(withID: theID)
|
||||
}
|
||||
}
|
||||
|
||||
func updateRecording(_ r: Recording) {
|
||||
try? run(sql: "UPDATE rec SET title = ?, appid = ?, notes = ? WHERE id = ? LIMIT 1;", bind: {
|
||||
self.bindTextOrNil($0, 1, r.title) && self.bindTextOrNil($0, 2, r.appId)
|
||||
&& self.bindTextOrNil($0, 3, r.notes) && self.bindInt64($0, 4, r.id)
|
||||
}) { stmt -> Void in
|
||||
sqlite3_step(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteRecording(_ r: Recording) throws -> Bool {
|
||||
_ = try? deleteRecordingLogs(r.id)
|
||||
return try run(sql: "DELETE FROM rec WHERE id = ? LIMIT 1;", bind: {
|
||||
self.bindInt64($0, 1, r.id)
|
||||
}) {
|
||||
try ifStep($0, SQLITE_DONE)
|
||||
return sqlite3_changes(dbPointer) > 0
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: read
|
||||
|
||||
func readRecording(_ stmt: OpaquePointer) -> Recording {
|
||||
let end = sqlite3_column_int64(stmt, 2)
|
||||
return Recording(id: sqlite3_column_int64(stmt, 0),
|
||||
start: sqlite3_column_int64(stmt, 1),
|
||||
stop: end == 0 ? nil : end,
|
||||
appId: readText(stmt, 3),
|
||||
title: readText(stmt, 4),
|
||||
notes: readText(stmt, 5))
|
||||
}
|
||||
|
||||
func ongoingRecording() -> Recording? {
|
||||
try? run(sql: "SELECT * FROM rec WHERE stop IS NULL LIMIT 1;", bind: nil) {
|
||||
try ifStep($0, SQLITE_ROW)
|
||||
return readRecording($0)
|
||||
}
|
||||
}
|
||||
|
||||
func allRecordings() -> [Recording]? {
|
||||
try? run(sql: "SELECT * FROM rec WHERE stop IS NOT NULL;", bind: nil) {
|
||||
allRows($0) { readRecording($0) }
|
||||
}
|
||||
}
|
||||
|
||||
func getRecording(withID: sqlite3_int64) throws -> Recording {
|
||||
try run(sql: "SELECT * FROM rec WHERE id = ? LIMIT 1;", bind: {
|
||||
self.bindInt64($0, 1, withID)
|
||||
}) {
|
||||
try ifStep($0, SQLITE_ROW)
|
||||
return readRecording($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK:
|
||||
|
||||
private struct RecordingLog: SQLTable {
|
||||
let rID: Int32
|
||||
let ts: Timestamp
|
||||
let domain: String
|
||||
static var createStatement: String {
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS recLog(
|
||||
rid INTEGER REFERENCES rec(id) ON DELETE CASCADE,
|
||||
ts INTEGER,
|
||||
domain TEXT
|
||||
);
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
extension SQLiteDatabase {
|
||||
|
||||
// MARK: write
|
||||
|
||||
func persistRecordingLogs(_ r: Recording) {
|
||||
guard let end = r.stop else {
|
||||
return
|
||||
}
|
||||
try? run(sql: """
|
||||
INSERT INTO recLog (rid, ts, domain) SELECT ?, ts, domain FROM req
|
||||
WHERE req.ts >= ? AND req.ts <= ?
|
||||
""", bind: {
|
||||
self.bindInt64($0, 1, r.id) && self.bindInt64($0, 2, r.start) && self.bindInt64($0, 3, end)
|
||||
}) {
|
||||
try ifStep($0, SQLITE_DONE)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteRecordingLogs(_ recId: sqlite3_int64, matchingDomain d: String? = nil) throws -> Int32 {
|
||||
try run(sql: "DELETE FROM recLog WHERE rid = ? \(d==nil ? "" : "AND domain = ?");", bind: {
|
||||
self.bindInt64($0, 1, recId) && (d==nil ? true : self.bindTextOrNil($0, 2,d))
|
||||
}) {
|
||||
try ifStep($0, SQLITE_DONE)
|
||||
return sqlite3_changes(dbPointer)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: read
|
||||
|
||||
func getRecordingsLogs(_ r: Recording) -> [RecordLog]? {
|
||||
try? run(sql: "SELECT domain, COUNT() FROM recLog WHERE rid = ? GROUP BY domain;", bind: {
|
||||
self.bindInt64($0, 1, r.id)
|
||||
}) {
|
||||
allRows($0) { (readText($0, 0), sqlite3_column_int($0, 1)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias RecordLog = (domain: String?, count: Int32)
|
||||
|
||||
@@ -1,50 +1,67 @@
|
||||
import UIKit
|
||||
|
||||
// MARK: Basic Alerts
|
||||
|
||||
func Alert(title: String?, text: String?, buttonText: String = "Dismiss") -> UIAlertController {
|
||||
let alert = UIAlertController(title: title, message: text, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: buttonText, style: .cancel, handler: nil))
|
||||
return alert
|
||||
}
|
||||
|
||||
func ErrorAlert(_ error: Error, buttonText: String = "Dismiss") -> UIAlertController {
|
||||
return Alert(title: "Error", text: error.localizedDescription, buttonText: buttonText)
|
||||
}
|
||||
|
||||
func AskAlert(title: String?, text: String?, buttonText: String = "Continue", buttonStyle: UIAlertAction.Style = .default, action: @escaping () -> Void) -> UIAlertController {
|
||||
let alert = Alert(title: title, text: text, buttonText: "Cancel")
|
||||
alert.addAction(UIAlertAction(title: buttonText, style: buttonStyle) { _ in action() })
|
||||
return alert
|
||||
}
|
||||
|
||||
extension UIAlertController {
|
||||
func presentIn(_ viewController: UIViewController?) {
|
||||
viewController?.present(self, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Basic Alerts
|
||||
|
||||
/// - Parameters:
|
||||
/// - buttonText: Default: `"Dismiss"`
|
||||
func Alert(title: String?, text: String?, buttonText: String = "Dismiss") -> UIAlertController {
|
||||
let alert = UIAlertController(title: title, message: text, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: buttonText, style: .cancel, handler: nil))
|
||||
return alert
|
||||
}
|
||||
|
||||
/// - Parameters:
|
||||
/// - buttonText: Default:`"Dismiss"`
|
||||
func ErrorAlert(_ error: Error, buttonText: String = "Dismiss") -> UIAlertController {
|
||||
return Alert(title: "Error", text: error.localizedDescription, buttonText: buttonText)
|
||||
}
|
||||
|
||||
/// - Parameters:
|
||||
/// - buttonText: Default: `"Dismiss"`
|
||||
func ErrorAlert(_ errorDescription: String, buttonText: String = "Dismiss") -> UIAlertController {
|
||||
return Alert(title: "Error", text: errorDescription, buttonText: buttonText)
|
||||
}
|
||||
|
||||
/// - 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")
|
||||
alert.addAction(UIAlertAction(title: buttonText, style: buttonStyle) { _ in action(alert) })
|
||||
return alert
|
||||
}
|
||||
|
||||
// MARK: Alert with multiple options
|
||||
|
||||
func AlertWithOptions(title: String?, text: String?, buttons: [String], lastIsDestructive: Bool = false, callback: @escaping (_ index: Int?) -> Void) -> UIAlertController {
|
||||
/// - Parameters:
|
||||
/// - buttons: Default: `[]`
|
||||
/// - lastIsDestructive: Default: `false`
|
||||
/// - cancelButtonText: Default: `"Dismiss"`
|
||||
func BottomAlert(title: String?, text: String?, buttons: [String] = [], lastIsDestructive: Bool = false, cancelButtonText: String = "Dismiss", callback: @escaping (_ index: Int?) -> Void) -> UIAlertController {
|
||||
let alert = UIAlertController(title: title, message: text, preferredStyle: .actionSheet)
|
||||
for (i, btn) in buttons.enumerated() {
|
||||
let dangerous = (lastIsDestructive && i + 1 == buttons.count)
|
||||
alert.addAction(UIAlertAction(title: btn, style: dangerous ? .destructive : .default) { _ in callback(i) })
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in callback(nil) })
|
||||
alert.addAction(UIAlertAction(title: cancelButtonText, style: .cancel) { _ in callback(nil) })
|
||||
return alert
|
||||
}
|
||||
|
||||
func AlertDeleteLogs(_ domain: String, latest: Timestamp, success: @escaping (_ tsMin: Timestamp) -> Void) -> UIAlertController {
|
||||
let sinceNow = TimestampNow() - latest
|
||||
let sinceNow = Timestamp.now() - latest
|
||||
var buttons = ["Last 5 minutes", "Last 15 minutes", "Last hour", "Last 24 hours", "Delete everything"]
|
||||
var times: [Timestamp] = [300, 900, 3600, 86400]
|
||||
while times.count > 0, times[0] < sinceNow {
|
||||
buttons.removeFirst()
|
||||
times.removeFirst()
|
||||
}
|
||||
return AlertWithOptions(title: "Delete logs", text: "Delete logs for domain '\(domain)'", buttons: buttons, lastIsDestructive: true) {
|
||||
return BottomAlert(title: "Delete logs", text: "Delete logs for domain '\(domain)'", buttons: buttons, lastIsDestructive: true, cancelButtonText: "Cancel") {
|
||||
guard let idx = $0 else {
|
||||
return
|
||||
}
|
||||
|
||||
82
main/Extensions/AutoLayout.swift
Normal file
82
main/Extensions/AutoLayout.swift
Normal file
@@ -0,0 +1,82 @@
|
||||
import UIKit
|
||||
|
||||
/*
|
||||
Readable Auto Layout Constraints
|
||||
|
||||
Usage:
|
||||
A.anchor =&= multiplier * B.anchor + constant | priority
|
||||
*/
|
||||
|
||||
infix operator =&= : AdditionPrecedence
|
||||
infix operator =<= : AdditionPrecedence
|
||||
infix operator =>= : AdditionPrecedence
|
||||
//infix operator | : AdditionPrecedence
|
||||
|
||||
/// Create and activate an `equal` constraint between left and right anchor. Format: `A.anchor =&= multiplier * B.anchor + constant | priority`
|
||||
@discardableResult func =&= <T>(l: NSLayoutAnchor<T>, r: NSLayoutAnchor<T>) -> NSLayoutConstraint { l.constraint(equalTo: r).on() }
|
||||
/// Create and activate a `lessThan` constraint between left and right anchor. Format: `A.anchor =<= multiplier * B.anchor + constant | priority`
|
||||
@discardableResult func =<= <T>(l: NSLayoutAnchor<T>, r: NSLayoutAnchor<T>) -> NSLayoutConstraint { l.constraint(lessThanOrEqualTo: r).on() }
|
||||
/// Create and activate a `greaterThan` constraint between left and right anchor. Format: `A.anchor =>= multiplier * B.anchor + constant | priority`
|
||||
@discardableResult func =>= <T>(l: NSLayoutAnchor<T>, r: NSLayoutAnchor<T>) -> NSLayoutConstraint { l.constraint(greaterThanOrEqualTo: r).on() }
|
||||
|
||||
extension NSLayoutDimension { // higher precedence, so multiply first
|
||||
/// Create intermediate anchor multiplier result.
|
||||
static func *(l: CGFloat, r: NSLayoutDimension) -> AnchorMultiplier { .init(anchor: r, m: l) }
|
||||
}
|
||||
|
||||
/// Intermediate `NSLayoutConstraint` anchor with multiplier supplement
|
||||
struct AnchorMultiplier {
|
||||
let anchor: NSLayoutDimension, m: CGFloat
|
||||
|
||||
/// Create and activate an `equal` constraint between left and right anchor. Format: `A.anchor =&= multiplier * B.anchor + constant | priority`
|
||||
@discardableResult static func =&=(l: NSLayoutDimension, r: Self) -> NSLayoutConstraint { l.constraint(equalTo: r.anchor, multiplier: r.m).on() }
|
||||
/// Create and activate a `lessThan` constraint between left and right anchor. Format: `A.anchor =<= multiplier * B.anchor + constant | priority`
|
||||
@discardableResult static func =<=(l: NSLayoutDimension, r: Self) -> NSLayoutConstraint { l.constraint(lessThanOrEqualTo: r.anchor, multiplier: r.m).on() }
|
||||
/// Create and activate a `greaterThan` constraint between left and right anchor. Format: `A.anchor =>= multiplier * B.anchor + constant | priority`
|
||||
@discardableResult static func =>=(l: NSLayoutDimension, r: Self) -> NSLayoutConstraint { l.constraint(greaterThanOrEqualTo: r.anchor, multiplier: r.m).on() }
|
||||
}
|
||||
|
||||
extension NSLayoutConstraint {
|
||||
/// Change `isActive`to `true` and return `self`
|
||||
func on() -> Self { isActive = true; return self }
|
||||
/// Change `constant`attribute and return `self`
|
||||
@discardableResult static func +(l: NSLayoutConstraint, r: CGFloat) -> NSLayoutConstraint { l.constant = r; return l }
|
||||
/// Change `constant` attribute and return `self`
|
||||
@discardableResult static func -(l: NSLayoutConstraint, r: CGFloat) -> NSLayoutConstraint { l.constant = -r; return l }
|
||||
/// Change `priority` attribute and return `self`
|
||||
@discardableResult static func |(l: NSLayoutConstraint, r: UILayoutPriority) -> NSLayoutConstraint { l.priority = r; return l }
|
||||
}
|
||||
|
||||
/*
|
||||
UIView extension to generate multiple constraints at once
|
||||
|
||||
Usage:
|
||||
child.anchor([.width, .height], to: parent) | .defaultLow
|
||||
*/
|
||||
|
||||
extension UIView {
|
||||
/// Edges that need the relation to flip arguments. For these we need to inverse the constant value and relation.
|
||||
private static let inverseItem: [NSLayoutConstraint.Attribute] = [.right, .bottom, .trailing, .lastBaseline, .rightMargin, .bottomMargin, .trailingMargin]
|
||||
|
||||
/// Create and active constraints for provided edges. Constraints will anchor the same edge on both `self` and `other`.
|
||||
/// - Parameters:
|
||||
/// - edges: List of constraint attributes, e.g. `[.top, .bottom, .left, .right]`
|
||||
/// - other: Instance to bind to, e.g. `UIView` or `UILayoutGuide`
|
||||
/// - margin: Used as constant value. Multiplier will always be `1.0`. If you need to change the multiplier, use single constraints instead. (Default: `0`)
|
||||
/// - rel: Constraint relation. (Default: `.equal`)
|
||||
/// - Returns: List of created and active constraints
|
||||
@discardableResult func anchor(_ edges: [NSLayoutConstraint.Attribute], to other: Any, margin: CGFloat = 0, if rel: NSLayoutConstraint.Relation = .equal) -> [NSLayoutConstraint] {
|
||||
edges.map {
|
||||
let (A, B) = UIView.inverseItem.contains($0) ? (other, self) : (self, other)
|
||||
return NSLayoutConstraint(item: A, attribute: $0, relatedBy: rel, toItem: B, attribute: $0, multiplier: 1, constant: margin).on()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element: NSLayoutConstraint {
|
||||
/// set `priority` on all elements and return same list
|
||||
@discardableResult static func |(l: Self, r: UILayoutPriority) -> Self {
|
||||
for x in l { x.priority = r }
|
||||
return l
|
||||
}
|
||||
}
|
||||
@@ -18,3 +18,15 @@ extension Array where Element == GroupedDomain {
|
||||
return GroupedDomain(domain: domain, total: t, blocked: b, lastModified: m, options: opt)
|
||||
}
|
||||
}
|
||||
|
||||
extension Recording {
|
||||
var fallbackTitle: String { get { "Unnamed Recording #\(id)" } }
|
||||
var duration: Timestamp? { get { stop == nil ? nil : stop! - start } }
|
||||
var durationString: String? { get { stop == nil ? nil : TimeFormat.from(duration!) } }
|
||||
}
|
||||
|
||||
extension Timestamp {
|
||||
func asDateTime() -> String { dateTimeFormat.string(from: self) }
|
||||
func toDate() -> Date { Date(timeIntervalSince1970: Double(self)) }
|
||||
static func now() -> Timestamp { Timestamp(Date().timeIntervalSince1970) }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
struct QLog {
|
||||
private init() {}
|
||||
@@ -55,15 +55,11 @@ extension String {
|
||||
ending = rld + "." + ending
|
||||
}
|
||||
return (domain: ending, host: parts.joined(separator: "."))
|
||||
|
||||
// var allDots = enumerated().compactMap { $1 == "." ? $0 : nil }
|
||||
// let d1 = allDots.popLast() // we dont care about TLD
|
||||
// guard let d2 = allDots.popLast() else {
|
||||
// return (domain: self, host: nil) // no subdomains, just plain SLD
|
||||
// }
|
||||
// // TODO: check third level domains
|
||||
//// let d3 = allDots.popLast()
|
||||
// return (String(suffix(count - d2 - 1)), String(prefix(d2)))
|
||||
}
|
||||
/// Returns `true` if String matches list of known second level domains (e.g., `co.uk`).
|
||||
func isKnownSLD() -> Bool {
|
||||
let parts = components(separatedBy: ".")
|
||||
return parts.count == 2 && listOfSLDs[parts.last!]?[parts.first!] ?? false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,4 +84,30 @@ extension DateFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
func TimestampNow() -> Timestamp { Timestamp(Date().timeIntervalSince1970) }
|
||||
struct TimeFormat {
|
||||
static func from(_ duration: Timestamp) -> String {
|
||||
String(format: "%02d:%02d", duration / 60, duration % 60)
|
||||
}
|
||||
static func from(_ duration: TimeInterval, millis: Bool = false) -> String {
|
||||
let t = Int(duration)
|
||||
if millis {
|
||||
let mil = Int(duration * 1000) % 1000
|
||||
return String(format: "%02d:%02d.%03d", t / 60, t % 60, mil)
|
||||
}
|
||||
return String(format: "%02d:%02d", t / 60, t % 60)
|
||||
}
|
||||
static func since(_ date: Date, millis: Bool = false) -> String {
|
||||
from(Date().timeIntervalSince(date), millis: millis)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIColor {
|
||||
static var sysBg: UIColor { get { if #available(iOS 13.0, *) { return .systemBackground } else { return .white } }}
|
||||
static var sysFg: UIColor { get { if #available(iOS 13.0, *) { return .label } else { return .black } }}
|
||||
}
|
||||
|
||||
extension UIEdgeInsets {
|
||||
init(all: CGFloat = 0, top: CGFloat? = nil, left: CGFloat? = nil, bottom: CGFloat? = nil, right: CGFloat? = nil) {
|
||||
self.init(top: top ?? all, left: left ?? all, bottom: bottom ?? all, right: right ?? all)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import Foundation
|
||||
let NotifyVPNStateChanged = NSNotification.Name("GlassVPNStateChanged") // VPNState!
|
||||
let NotifyFilterChanged = NSNotification.Name("PSIFilterSettingsChanged") // nil!
|
||||
let NotifyLogHistoryReset = NSNotification.Name("PSILogHistoryReset") // nil!
|
||||
let NotifyRecordingChanged = NSNotification.Name("PSIRecordingChanged") // (Recording, deleted: Bool)!
|
||||
|
||||
|
||||
extension NSNotification.Name {
|
||||
|
||||
@@ -3,8 +3,8 @@ import UIKit
|
||||
extension GroupedDomain {
|
||||
var detailCellText: String { get {
|
||||
return blocked > 0
|
||||
? "\(dateTimeFormat.string(from: lastModified)) — \(blocked)/\(total) blocked"
|
||||
: "\(dateTimeFormat.string(from: lastModified)) — \(total)"
|
||||
? "\(lastModified.asDateTime()) — \(blocked)/\(total) blocked"
|
||||
: "\(lastModified.asDateTime()) — \(total)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,29 +59,29 @@ extension IncrementalDataSourceUpdate {
|
||||
func insertRow(_ obj: GroupedDomain, at index: Int) {
|
||||
dataSource.insert(obj, at: index)
|
||||
ifDisplayed {
|
||||
self.tableView.insertRows(at: [IndexPath(row: index, section: 0)], with: .left)
|
||||
self.tableView.insertRows(at: [IndexPath(row: index)], with: .left)
|
||||
}
|
||||
}
|
||||
func moveRow(_ obj: GroupedDomain, from: Int, to: Int) {
|
||||
dataSource.remove(at: from)
|
||||
dataSource.insert(obj, at: to)
|
||||
ifDisplayed {
|
||||
let source = IndexPath(row: from, section: 0)
|
||||
let source = IndexPath(row: from)
|
||||
let cell = self.tableView.cellForRow(at: source)
|
||||
cell?.detailTextLabel?.text = obj.detailCellText
|
||||
self.tableView.moveRow(at: source, to: IndexPath(row: to, section: 0))
|
||||
self.tableView.moveRow(at: source, to: IndexPath(row: to))
|
||||
}
|
||||
}
|
||||
func replaceRow(_ obj: GroupedDomain, at index: Int) {
|
||||
dataSource[index] = obj
|
||||
ifDisplayed {
|
||||
self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
|
||||
self.tableView.reloadRows(at: [IndexPath(row: index)], with: .automatic)
|
||||
}
|
||||
}
|
||||
func deleteRow(at index: Int) {
|
||||
dataSource.remove(at: index)
|
||||
ifDisplayed {
|
||||
self.tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
|
||||
self.tableView.deleteRows(at: [IndexPath(row: index)], with: .automatic)
|
||||
}
|
||||
}
|
||||
func replaceData(with newData: [GroupedDomain]) {
|
||||
@@ -91,3 +91,8 @@ extension IncrementalDataSourceUpdate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension IndexPath {
|
||||
/// Convenience init with `section: 0`
|
||||
public init(row: Int) { self.init(row: row, section: 0) }
|
||||
}
|
||||
|
||||
@@ -10,14 +10,10 @@ fileprivate extension FileManager {
|
||||
func internalDB() -> URL {
|
||||
appGroupDir().appendingPathComponent("dns-logs.sqlite")
|
||||
}
|
||||
func appGroupIPC() -> URL {
|
||||
appGroupDir().appendingPathComponent("data-exchange.dat")
|
||||
}
|
||||
}
|
||||
|
||||
extension URL {
|
||||
static func exportDir() -> URL { FileManager.default.exportDir() }
|
||||
static func appGroupDir() -> URL { FileManager.default.appGroupDir() }
|
||||
static func internalDB() -> URL { FileManager.default.internalDB() }
|
||||
static func appGroupIPC() -> URL { FileManager.default.appGroupIPC() }
|
||||
}
|
||||
83
main/Recordings/TVCPreviousRecords.swift
Normal file
83
main/Recordings/TVCPreviousRecords.swift
Normal file
@@ -0,0 +1,83 @@
|
||||
import UIKit
|
||||
|
||||
class TVCPreviousRecords: UITableViewController, EditActionsRemove {
|
||||
private var dataSource: [Recording] = []
|
||||
|
||||
override func viewDidLoad() {
|
||||
dataSource = DBWrp.listOfRecordings().reversed() // newest on top
|
||||
NotifyRecordingChanged.observe(call: #selector(recordingDidChange(_:)), on: self)
|
||||
}
|
||||
|
||||
func insertAndEditRecording(_ r: Recording) {
|
||||
insertNewRecord(r)
|
||||
editRecord(r, isNewRecording: true)
|
||||
}
|
||||
|
||||
@objc private func recordingDidChange(_ notification: Notification) {
|
||||
let (new, deleted) = notification.object as! (Recording, Bool)
|
||||
if let i = dataSource.firstIndex(where: { $0.id == new.id }) {
|
||||
if deleted {
|
||||
dataSource.remove(at: i)
|
||||
tableView.deleteRows(at: [IndexPath(row: i)], with: .automatic)
|
||||
} else {
|
||||
dataSource[i] = new
|
||||
tableView.reloadRows(at: [IndexPath(row: i)], with: .automatic)
|
||||
}
|
||||
} else if !deleted {
|
||||
insertNewRecord(new)
|
||||
}
|
||||
}
|
||||
|
||||
private func insertNewRecord(_ record: Recording) {
|
||||
dataSource.insert(record, at: 0)
|
||||
tableView.insertRows(at: [IndexPath(row: 0)], with: .top)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Delegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
||||
editRecord(dataSource[indexPath.row])
|
||||
}
|
||||
|
||||
private func editRecord(_ record: Recording, isNewRecording: Bool = false) {
|
||||
performSegue(withIdentifier: "editRecordSegue", sender: (record, isNewRecording))
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if segue.identifier == "editRecordSegue" {
|
||||
let (record, newlyCreated) = sender as! (Recording, Bool)
|
||||
let target = segue.destination as! VCEditRecording
|
||||
target.record = record
|
||||
target.deleteOnCancel = newlyCreated
|
||||
} else if segue.identifier == "openRecordDetailsSegue" {
|
||||
if let i = tableView.indexPathForSelectedRow {
|
||||
(segue.destination as? TVCRecordingDetails)?.record = dataSource[i.row]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
dataSource.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PreviousRecordCell")!
|
||||
let x = dataSource[indexPath.row]
|
||||
cell.textLabel?.text = x.title ?? x.fallbackTitle
|
||||
cell.textLabel?.textColor = (x.title == nil) ? .systemGray : nil
|
||||
cell.detailTextLabel?.text = "at \(x.start.asDateTime()), duration: \(x.durationString ?? "?")"
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Editing
|
||||
|
||||
func editableRowCallback(_ index: IndexPath, _ action: RowAction, _ userInfo: Any?) -> Bool {
|
||||
DBWrp.recordingDelete(self.dataSource[index.row])
|
||||
return true
|
||||
}
|
||||
}
|
||||
37
main/Recordings/TVCRecordingDetails.swift
Normal file
37
main/Recordings/TVCRecordingDetails.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
import UIKit
|
||||
|
||||
class TVCRecordingDetails: UITableViewController, EditActionsRemove {
|
||||
var record: Recording!
|
||||
private var dataSource: [RecordLog]!
|
||||
|
||||
override func viewDidLoad() {
|
||||
title = record.title ?? record.fallbackTitle
|
||||
dataSource = DBWrp.recordingDetails(record)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
dataSource.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PreviousRecordDetailCell")!
|
||||
let x = dataSource[indexPath.row]
|
||||
cell.textLabel?.text = x.domain
|
||||
cell.detailTextLabel?.text = "\(x.count)"
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Editing
|
||||
|
||||
func editableRowCallback(_ index: IndexPath, _ action: RowAction, _ userInfo: Any?) -> Bool {
|
||||
if DBWrp.recordingDeleteDetails(record, domain: self.dataSource[index.row].domain) {
|
||||
self.dataSource.remove(at: index.row)
|
||||
self.tableView.deleteRows(at: [index], with: .automatic)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
125
main/Recordings/VCEditRecording.swift
Normal file
125
main/Recordings/VCEditRecording.swift
Normal file
@@ -0,0 +1,125 @@
|
||||
import UIKit
|
||||
|
||||
class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate {
|
||||
var record: Recording!
|
||||
var deleteOnCancel: Bool = false
|
||||
|
||||
@IBOutlet private var buttonCancel: UIBarButtonItem!
|
||||
@IBOutlet private var buttonSave: UIBarButtonItem!
|
||||
@IBOutlet private var inputTitle: UITextField!
|
||||
@IBOutlet private var inputNotes: UITextView!
|
||||
@IBOutlet private var inputDetails: UITextView!
|
||||
@IBOutlet private var noteBottom: NSLayoutConstraint!
|
||||
|
||||
override func viewDidLoad() {
|
||||
inputTitle.placeholder = record.fallbackTitle
|
||||
inputTitle.text = record.title
|
||||
inputNotes.text = record.notes
|
||||
inputDetails.text = """
|
||||
Start: \(record.start.asDateTime())
|
||||
End: \(record.stop?.asDateTime() ?? "?")
|
||||
Duration: \(record.durationString ?? "?")
|
||||
"""
|
||||
validateSaveButton()
|
||||
if deleteOnCancel { // mark as destructive
|
||||
buttonCancel.tintColor = .systemRed
|
||||
if #available(iOS 13.0, *) {
|
||||
isModalInPresentation = true
|
||||
}
|
||||
}
|
||||
UIResponder.keyboardWillShowNotification.observe(call: #selector(keyboardWillShow), on: self)
|
||||
UIResponder.keyboardWillHideNotification.observe(call: #selector(keyboardWillHide), on: self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Save & Cancel Buttons
|
||||
|
||||
@IBAction func didTapSave(_ sender: UIBarButtonItem) {
|
||||
if deleteOnCancel { // aka newly created
|
||||
// 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) {
|
||||
DBWrp.recordingUpdate(self.record)
|
||||
DBWrp.recordingPersist(self.record)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func didTapCancel(_ sender: UIBarButtonItem) {
|
||||
QLog.Debug("discard edit of record #\(record.id)")
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
if deleteOnCancel {
|
||||
QLog.Debug("deleting record #\(record.id)")
|
||||
DBWrp.recordingDelete(record)
|
||||
deleteOnCancel = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Handle Keyboard & Notes Frame
|
||||
|
||||
private var isEditingNotes: Bool = false
|
||||
private var keyboardHeight: CGFloat = 0
|
||||
|
||||
@IBAction func hideKeyboard() { view.endEditing(false) }
|
||||
|
||||
func textViewDidBeginEditing(_ textView: UITextView) {
|
||||
if textView == inputNotes {
|
||||
isEditingNotes = true
|
||||
updateKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
func textViewDidEndEditing(_ textView: UITextView) {
|
||||
if textView == inputNotes {
|
||||
isEditingNotes = false
|
||||
updateKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func keyboardWillShow(_ notification: NSNotification) {
|
||||
keyboardHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height ?? 0
|
||||
updateKeyboard()
|
||||
}
|
||||
|
||||
@objc func keyboardWillHide(_ notification: NSNotification) {
|
||||
keyboardHeight = 0
|
||||
updateKeyboard()
|
||||
}
|
||||
|
||||
private func updateKeyboard() {
|
||||
guard let parent = inputNotes.superview, let stack = parent.superview else {
|
||||
return
|
||||
}
|
||||
let adjust = (isEditingNotes && keyboardHeight > 0)
|
||||
stack.subviews.forEach{ $0.isHidden = (adjust && $0 != parent) }
|
||||
|
||||
let title = parent.subviews.first as! UILabel
|
||||
title.font = .preferredFont(forTextStyle: adjust ? .subheadline : .title2)
|
||||
title.sizeToFit()
|
||||
title.frame.size.width = parent.frame.width
|
||||
|
||||
noteBottom.constant = adjust ? view.frame.height - stack.frame.maxY - keyboardHeight : 0
|
||||
}
|
||||
|
||||
|
||||
// MARK: TextField & TextView Delegate
|
||||
|
||||
func textFieldDidChangeSelection(_ _: UITextField) { validateSaveButton() }
|
||||
func textViewDidChange(_ _: UITextView) { validateSaveButton() }
|
||||
|
||||
private func validateSaveButton() {
|
||||
let changed = (inputTitle.text != record.title ?? "" || inputNotes.text != record.notes ?? "")
|
||||
buttonSave.isEnabled = changed || deleteOnCancel // always allow save for new recordings
|
||||
}
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
textField == inputTitle ? inputNotes.becomeFirstResponder() : true
|
||||
}
|
||||
}
|
||||
133
main/Recordings/VCRecordings.swift
Normal file
133
main/Recordings/VCRecordings.swift
Normal file
@@ -0,0 +1,133 @@
|
||||
import UIKit
|
||||
|
||||
class VCRecordings: UIViewController, UINavigationControllerDelegate {
|
||||
private var currentRecording: Recording?
|
||||
private var recordingTimer: Timer?
|
||||
|
||||
@IBOutlet private var timeLabel: UILabel!
|
||||
@IBOutlet private var startButton: UIButton!
|
||||
@IBOutlet private var startNewRecView: UIView!
|
||||
private var prevRecController: UINavigationController!
|
||||
|
||||
override func viewDidLoad() {
|
||||
prevRecController = (children.first as! UINavigationController)
|
||||
prevRecController.delegate = self
|
||||
// Duplicate font attributes but set monospace
|
||||
let traits = timeLabel.font.fontDescriptor.object(forKey: .traits) as? [UIFontDescriptor.TraitKey: Any] ?? [:]
|
||||
let weight = traits[.weight] as? CGFloat ?? UIFont.Weight.regular.rawValue
|
||||
timeLabel.font = UIFont.monospacedDigitSystemFont(ofSize: timeLabel.font.pointSize, weight: UIFont.Weight(rawValue: weight))
|
||||
// hide timer if not running
|
||||
updateUI(setRecording: false, animated: false)
|
||||
currentRecording = DBWrp.recordingGetCurrent()
|
||||
|
||||
if !UserDefaults.standard.bool(forKey: "didShowTutorialRecordings") {
|
||||
self.perform(#selector(showTutorial), with: nil, afterDelay: 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
if currentRecording != nil { startTimer(animate: false) }
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
stopTimer(animate: false)
|
||||
}
|
||||
|
||||
func navigationController(_ navigationController: UINavigationController, willShow vc: UIViewController, animated: Bool) {
|
||||
let isRoot = (vc == navigationController.viewControllers.first)
|
||||
UIView.animate(withDuration: 0.3) {
|
||||
self.startNewRecView.isHidden = !isRoot // hide "new recording" if details open
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Start New Recording
|
||||
|
||||
@IBAction private func startRecordingButtonTapped(_ sender: UIButton) {
|
||||
if recordingTimer == nil {
|
||||
currentRecording = DBWrp.recordingStartNew()
|
||||
startTimer(animate: true)
|
||||
} else {
|
||||
stopTimer(animate: true)
|
||||
DBWrp.recordingStop(¤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 {
|
||||
return
|
||||
}
|
||||
recordingTimer = Timer.repeating(0.086, call: #selector(timerCallback(_:)), on: self, userInfo: r.start.toDate())
|
||||
updateUI(setRecording: true, animated: animate)
|
||||
}
|
||||
|
||||
@objc private func timerCallback(_ sender: Timer) {
|
||||
timeLabel.text = TimeFormat.since(sender.userInfo as! Date, millis: true)
|
||||
}
|
||||
|
||||
private func stopTimer(animate: Bool) {
|
||||
recordingTimer?.invalidate()
|
||||
recordingTimer = nil
|
||||
updateUI(setRecording: false, animated: animate)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Tutorial View Controller
|
||||
|
||||
@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 {
|
||||
UserDefaults.standard.set(true, forKey: "didShowTutorialRecordings")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class TVCHostDetails: UITableViewController {
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "HostDetailCell")!
|
||||
let src = dataSource[indexPath.row]
|
||||
cell.textLabel?.text = dateTimeFormat.string(from: src.ts)
|
||||
cell.textLabel?.text = src.ts.asDateTime()
|
||||
cell.imageView?.image = (src.blocked ? UIImage(named: "shield-x") : nil)
|
||||
return cell
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ class TVCFilter: UITableViewController, EditActionsRemove {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
if #available(iOS 10.0, *) {
|
||||
tableView.refreshControl = UIRefreshControl(call: #selector(reloadDataSource), on: self)
|
||||
}
|
||||
// if #available(iOS 10.0, *) {
|
||||
// tableView.refreshControl = UIRefreshControl(call: #selector(reloadDataSource), on: self)
|
||||
// }
|
||||
NotifyFilterChanged.observe(call: #selector(reloadDataSource), on: self)
|
||||
reloadDataSource()
|
||||
}
|
||||
@@ -18,6 +18,27 @@ class TVCFilter: UITableViewController, EditActionsRemove {
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
@IBAction private func addNewFilter() {
|
||||
let desc: String
|
||||
switch currentFilter {
|
||||
case .blocked: desc = "Enter the domain name you wish to block."
|
||||
case .ignored: desc = "Enter the domain name you wish to ignore."
|
||||
default: return
|
||||
}
|
||||
let alert = AskAlert(title: "Create new filter", text: desc, buttonText: "Add") {
|
||||
guard let dom = $0.textFields?.first?.text else {
|
||||
return
|
||||
}
|
||||
guard dom.contains("."), !dom.isKnownSLD() else {
|
||||
ErrorAlert("Entered domain is not valid. Filter can't match country TLD only.").presentIn(self)
|
||||
return
|
||||
}
|
||||
DBWrp.updateFilter(dom, add: self.currentFilter)
|
||||
}
|
||||
alert.addTextField { $0.placeholder = "cdn.domain.tld" }
|
||||
alert.presentIn(self)
|
||||
}
|
||||
|
||||
// MARK: - Table View Delegate
|
||||
|
||||
override func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int { dataSource.count }
|
||||
@@ -25,16 +46,47 @@ class TVCFilter: UITableViewController, EditActionsRemove {
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "DomainFilterCell")!
|
||||
cell.textLabel?.text = dataSource[indexPath.row]
|
||||
if cell.gestureRecognizers?.isEmpty ?? true {
|
||||
cell.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(didLongTap)))
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK: - Editing
|
||||
|
||||
func editableRowCallback(_ index: IndexPath, _ action: RowAction, _ userInfo: Any?) -> Bool {
|
||||
let domain = self.dataSource[index.row]
|
||||
let domain = dataSource[index.row]
|
||||
DBWrp.updateFilter(domain, remove: currentFilter)
|
||||
self.dataSource.remove(at: index.row)
|
||||
self.tableView.deleteRows(at: [index], with: .automatic)
|
||||
dataSource.remove(at: index.row)
|
||||
tableView.deleteRows(at: [index], with: .automatic)
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - Long Press Gesture
|
||||
|
||||
private var cellTitleCopy: String?
|
||||
|
||||
@objc private func didLongTap(_ sender: UILongPressGestureRecognizer) {
|
||||
guard let cell = sender.view as? UITableViewCell else {
|
||||
return
|
||||
}
|
||||
if sender.state == .began {
|
||||
cellTitleCopy = cell.textLabel?.text
|
||||
self.becomeFirstResponder()
|
||||
let menu = UIMenuController.shared
|
||||
// menu.setTargetRect(CGRect(origin: sender.location(in: cell), size: CGSize.zero), in: cell)
|
||||
menu.setTargetRect(cell.bounds, in: cell)
|
||||
menu.setMenuVisible(true, animated: true)
|
||||
}
|
||||
}
|
||||
override var canBecomeFirstResponder: Bool { get { true } }
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
action == #selector(UIResponderStandardEditActions.copy)
|
||||
}
|
||||
|
||||
override func copy(_ sender: Any?) {
|
||||
UIPasteboard.general.string = cellTitleCopy
|
||||
cellTitleCopy = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,11 +52,18 @@ class TVCSettings: UITableViewController {
|
||||
// }.presentIn(self)
|
||||
}
|
||||
|
||||
@IBAction func resetTutorialAlerts(_ sender: UIButton) {
|
||||
UserDefaults.standard.removeObject(forKey: "didShowTutorialAppWelcome")
|
||||
UserDefaults.standard.removeObject(forKey: "didShowTutorialRecordings")
|
||||
Alert(title: sender.titleLabel?.text,
|
||||
text: "\nDone.\n\nYou may need to restart the application.").presentIn(self)
|
||||
}
|
||||
|
||||
@IBAction func clearDatabaseResults(_ sender: Any) {
|
||||
AskAlert(title: "Clear results?", text: """
|
||||
You are about to delete all results that have been logged in the past. Your preference for blocked and ignored domains is preserved.
|
||||
Continue?
|
||||
""", buttonText: "Delete", buttonStyle: .destructive) {
|
||||
AskAlert(title: "Clear results?", text:
|
||||
"You are about to delete all results that have been logged in the past. " +
|
||||
"Your preferences for blocked and ignored domains are preserved.\n" +
|
||||
"Continue?", buttonText: "Delete", buttonStyle: .destructive) { _ in
|
||||
DBWrp.deleteHistory()
|
||||
}.presentIn(self)
|
||||
}
|
||||
|
||||
@@ -5,13 +5,40 @@ class TBCMain: UITabBarController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// perform(#selector(showWelcomeMessage), with: nil, afterDelay: 3)
|
||||
NotifyVPNStateChanged.observe(call: #selector(vpnStateChanged(_:)), on: self)
|
||||
changedState(currentVPNState)
|
||||
|
||||
if !UserDefaults.standard.bool(forKey: "didShowTutorialAppWelcome") {
|
||||
self.perform(#selector(showWelcomeMessage), with: nil, afterDelay: 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func showWelcomeMessage() {
|
||||
performSegue(withIdentifier: "welcome", sender: nil)
|
||||
let x = TutorialSheet()
|
||||
x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString()
|
||||
.h1("Welcome\n")
|
||||
.normal("\nAppCheck helps you identify which applications communicate with third parties. " +
|
||||
"It does so by logging network requests. " +
|
||||
"AppCheck learns only the destination addresses, not the actual data that is exchanged." +
|
||||
"\n\n" +
|
||||
"Your data belongs to you. " +
|
||||
"Therefore, monitoring and analysis take place on your device only. " +
|
||||
"The app does not share any data with us or any other third-party. " +
|
||||
"Unless you choose to.")
|
||||
))
|
||||
x.addSheet().addArrangedSubview(QuickUI.text(attributed: NSMutableAttributedString()
|
||||
.h1("How it works\n")
|
||||
.normal("\nAppCheck creates a local VPN tunnel to intercept all network connections. " +
|
||||
"For each connection AppCheck looks into the DNS headers only, namely the domain names. " +
|
||||
"\n" +
|
||||
"These domain names are logged in the background while the VPN is active. " +
|
||||
"That means, AppCheck does not have to be active in the foreground. " +
|
||||
"You can close the app and come back later to see the results."
|
||||
)
|
||||
))
|
||||
x.present {
|
||||
UserDefaults.standard.set(true, forKey: "didShowTutorialAppWelcome")
|
||||
}
|
||||
}
|
||||
|
||||
@objc func vpnStateChanged(_ notification: Notification) {
|
||||
|
||||
Reference in New Issue
Block a user